Olivier Couthaud

Jarvius

My first AI assistant: a Discord bot you can type to or talk to, and the direct ancestor of what became VOS.

I built Jarvius because I wanted something like Jarvis in the Iron Man films, an assistant I could just talk to, and I wanted it to be useful on an ordinary day. Most of the time that meant the walk to class. I would send it voice messages while walking, and tell it to answer in text or in voice depending on whether I could listen or had to read. That worked, and it is the part I enjoyed most.

It started in late February 2025 as a Discord bot, because Discord was the quickest place to try it. It already handles accounts, text chats and voice calls, so I could skip building an interface and go straight to the interesting part. In the code the bot calls itself Jarbius. Jarvius is what I call the project now.

You type to it, or reply to one of its messages, and it answers in a sentence or two. The answers come from a language model, the kind of program behind chatbots, running on my own laptop instead of in someone else's cloud. That made it free to run, but it was only available when my laptop was on. It can also join a voice call and read its replies out loud, like a receptionist sitting in the call who answers what you typed, and in the first versions it could listen as well, writing down what was said and answering that. In the last version I wrote, it only speaks. It remembered what each person had said earlier, so a follow-up question made sense.

Youtype The bota program in Discord Language modelon a separate computer your message text, or read aloud your message + chat a short reply Youtype The bota program in Discord Language modelon a separate computer your message text, or read aloud your message + chat a short reply
Diagram. The whole loop in one line: you, the bot, the model, and back.

Around that I added two joke commands that print random words for farts and poops, and a party game called Death By AI. Everyone gets a scenario, types what they would do, and the model writes a short, dark little story about it and decides whether that person survived. Five rounds, and whoever survived the most wins.

It was never a polished thing. Each time it needed to listen or speak, it set up the speech software again from the start, which made it slow. The memory disappeared whenever the bot restarted. When I created the project I described it as a team of AI agents. In the Discord version that was never true. It was one model answering one message at a time.

Jarvius is the direct ancestor of VOS Gen 1, which is the same project carried on from February to September 2025. For that version I moved off my laptop to Google's Gemini AI service, with several AI helpers passing work to each other and a database to remember things between runs. That cost me far more money than it should have. I did learn a lot from it, about working with APIs and cloud models and about keeping track of what you are spending. I even gave the agents a tool to check how much they had spent. After it came VOS, which I built with Roman Slack.

What's next. I've stopped working on the Discord bot. If I picked it up again I would load the speech models once at start-up so replies come back faster.

Jarvius began as a Discord bot in Python, using discord.py. It ran a handful of commands, sent everything else to a language model, could speak its replies in a voice call, remembered what each person had said, and ran a party game. Jarvius is the direct ancestor of VOS Gen 1: the same project, with the Discord bot as its first stage, from February to September 2025.

What I wanted

I wanted Jarvis from the Iron Man films: an assistant you talk to out loud, that answers back and remembers you. The way I actually used it was on the walk to class. I would send it voice messages and ask it to reply in text or in voice, depending on whether I could listen or had to read. That worked. Discord was the shortcut. It already gives you accounts, text channels and voice calls, and a bot can sit in a call, so there was no interface to build before testing the idea. Every answer goes through Ollama, which runs open models on a machine you control. For me that was my own laptop, so the bot was not up around the clock, but it cost nothing to run. The first model was llama3.2:1b. By the end the default was dolphin3.

What is Ollama?

A program that downloads open language models and runs them on a machine you control, behind a small web address. The bot sends it a list of messages and gets text back, which is the same job a paid API does, without the bill. The cost is that you need a computer that can run the model.

When I created the project I described it as using a team of AI agents. The Discord version never had one. It is one model behind one short instruction. The agents belong to the Gemini version that came after.

How it grew

Six stops from the first text bot to VOS.

Text botlate Feb 2025 Voicesame day Memorysame day Death By AIMar 2025 VOS Gen 1to Sep 2025 VOSSep 2025 on Text botlate Feb 2025 Voicesame day Memorysame day Death By AIMar 2025 VOS Gen 1to Sep 2025 VOSSep 2025 on
  1. Text bot. A Discord bot that answers prefix commands: ping, help, and two joke ones. Anything it doesn't recognise goes to a small model, llama3.2:1b, running in Ollama on a separate machine, with an instruction to keep answers to two sentences.
  2. Voice. On the same first day I wrote the voice side: join a voice channel, transcribe speech with Whisper and answer out loud with Parler-TTS. "hey jarbius" became the phrase that meant you were talking to it.
  3. Memory. It started keeping each person's conversation, so follow-ups worked, and a reply to one of its messages counted as talking to it without a prefix. The default model ended up as dolphin3.
  4. Death By AI. A separate party-game bot where the model narrates what happens to each player and decides who survives.
  5. VOS Gen 1. A Python assistant on Google's Gemini API, with several AI agents handing work to each other and a Weaviate database for memory. The direct successor of the Discord bot, and the same project. It got expensive: I spent far more on Gemini than I meant to. It taught me a lot about APIs, cloud models and tracking usage, and I gave the agents a tool to check how much they had spent.
  6. VOS. The next generation, which I built with Roman Slack from September 2025. It has its own page.

Orange marks the current stop.

How it works

There are three ways to get its attention: start a message with "hey jarbius", reply to one of its messages, or use a command prefix. If the first word is a known command it runs that directly: ping, help, memory, join, leave, generate, and the two joke ones, fart and poop, which print up to 100 random synonyms. Anything else goes to the model. The bot saves your message, builds a prompt from a short system instruction plus your recent history, sends it to Ollama and posts the answer. In the last version these normal chat replies run on a joke persona I swapped in, not on the Jarbius instruction. The Jarbius instruction is still what generate uses. Both ask for two sentences at most.

Follow one message through the bot

Two routes through the same diagram. The typed route is the simple one. The spoken route is the call-listening loop, which the last version of the code no longer starts.

Discordtext channel main.pydiscord.py MemoryManagerRAM, 200 each Ollamaseparate machine Parler-TTSmini v1 Whispermedium.en Voice channelthe call spoken route: not started in the last version Discordtext channel main.pydiscord.py MemoryManagerRAM, 200 each Ollamaseparatemachine Parler-TTSmini v1 Whispermedium.en Voice channelthe call spoken route: not started in the last version

Typed route

  1. You write "hey jarbius", or reply to one of its messages, in a text channel. The bot sees it.
  2. The bot checks the first word. If it is a command such as ping, help or fart, it runs it and replies. The model is not involved.
  3. Otherwise it files your message under your server and your name in memory, and pulls back what you have said to it so far, up to 200 messages.
  4. It sends a short system instruction plus that history to Ollama. The instruction asks for two sentences at most. Ollama answers with text.
  5. The bot saves the reply to memory too, then posts it in the channel.
  6. If the bot is sitting in a voice channel, the reply is also turned into speech by Parler-TTS and played into the call.

Spoken route

  1. The bot sits in the call and records everyone in it as 48 kHz stereo audio (each person's audio is joined end to end).
  2. About every tenth of a second the loop measures how loud the recording is so far. If that is above 300 it counts as speech; after three seconds with no new reading above 300 the clip is closed. A clip is also cut at 30 seconds. Anything shorter than a second is thrown away.
  3. The clip is saved as a temporary audio file and handed to Whisper.
  4. Whisper returns a transcript. If "hey jarbius" appears anywhere in it, whatever follows is the request. Otherwise the clip is ignored.
  5. From here it is the typed route: a command runs directly, anything else goes through memory and Ollama.
  6. The reply is posted as text, with what it heard, and spoken into the call through Parler-TTS.

Orange boxes and arrows are the current step. The volume line of 300, the 3 second pause, the 30 second cap and the 1 second minimum are the settings the bot used.

Speaking back uses Parler-TTS mini v1. It takes the reply text plus a plain sentence describing the voice, and mine asks for a clear speaker on a close, clean recording. The result is played into the call through ffmpeg. If the bot is already talking, a new reply cuts the old one off. The listening side records the whole call, measures volume, and hands finished clips to Whisper.

What is Whisper?

An open speech-recognition model from OpenAI. You give it an audio file and it returns the text. The medium.en version is a mid-sized model trained on English only. It is more accurate than the small ones and slower, which matters when you run it on every clip.

What does the volume check actually measure?

It is the root mean square, or RMS, of the audio samples: square each sample, average them, take the square root. That turns a burst of sound into one loudness number. A quiet room gives a small one and speech a large one. The audio here is 16-bit, so single samples can reach about 32,000, and I set the line between quiet and speech at 300.

The wake phrase check is a plain text match on "hey jarbius" in the transcript, so a transcript that spells the name any other way is ignored. The last version of the code no longer starts the in-call listener: the bot joins your call, says so, and tells you to use generate to make it speak.

Memory

Memory is a small class that keeps the last 200 messages for each person on each server, and a second log of the last 200 for the server as a whole. Each entry is a role, the text and a time. When the model is called, the prompt is a short system instruction plus that one person's history. That is why a follow-up like "and what about the second one?" works, and also why the bot has no idea what anyone else in the channel just said. The server-wide log is only counted by the memory command, which can show counts or clear one person or everything.

add_messageserver, person, role Server loglast 200, only counted by stats Per-person historylast 200, per person per server System prompt+ that history Ollama add_messageserver, person, role Server loglast 200, onlycounted by stats Per-person historylast 200, per personper server System prompt+ that history Ollama
Diagram. Where a message is stored and which part of it the model gets to see. Orange is what goes into the prompt.

All of it sits in RAM, so a restart wipes it. The later Gemini version replaced it with Weaviate, which is a database that can keep memories between runs.

What is Weaviate?

A vector database. It stores each piece of text as a long list of numbers that captures roughly what it means, so a search can match on meaning and not just exact words. It also keeps everything on disk, so what it has stored survives a restart.

Death By AI

The game is a separate program in the same project, with its own commands and no shared code with the assistant. It uses the same model on the same Ollama machine. One person creates a game in a channel, others join, and the creator starts it. It works with a single player too. There is one game per channel, and it closes itself after ten minutes of silence.

Scenariorandom player, 30 s Answerseveryone, 60 s Story + verdictdolphin3, XML reply Revealword by word Scoreboardsurvivals per player next round, 5 rounds by default Scenariorandom player, 30 s Answerseveryone, 60 s Story + verdictdolphin3, XML reply Revealword by word Scoreboardsurvivals per player next round, 5 rounds by default
Diagram. One round. The orange box is the only place the model is called.

A random player is asked for a scenario and gets 30 seconds. If they don't write one, the bot picks one of twenty built-in scenarios. Everyone then has 60 seconds to say what they do, and anyone who doesn't answer dies. Each answer goes to dolphin3 together with the scenario. The instruction asks for a small block of XML: a short reflection on the scenario, the player's approach and the story, then a story of at most three sentences, then a verdict of SURVIVED or DIED with a one-line reason. The narrator is told to be a dark, crude comedian. The story is revealed by editing one message word by word, and a scoreboard follows each round.

Game settings the creator can change
SettingDefaultAllowed
Rounds5a whole number
Time to write a scenario30 s10 to 120 s
Time to answer60 s10 to 300 s
Story revealwordcharacter, word or instant
Delay between reveals0.2 s0.05 to 1 s

A small model doesn't always answer in the format it was asked for, so the parser is forgiving. It adds the missing wrapper tag, escapes stray ampersands, and if it still can't read the answer, the player survives. A model error counts as survival as well. So a broken answer never kills anyone.

What didn't work

Speech worked, but slowly. The speech models are loaded from scratch for every reply and for every clip, rather than once when the bot starts. That is the obvious first thing to fix. I also rewrote the call-listening code a few times as I changed how the bot receives audio.

Memory lives in RAM, so a restart forgets everyone, and it only ever follows one person's thread, never a group conversation. Everything depends on the Ollama machine being on, and that was my laptop, so when it was off the bot did not answer. The wake phrase is an exact text match, as above. Moving to Gemini later fixed the laptop problem and created a cost problem.

What's unfinished

I've stopped working on the Discord bot. The work went on as VOS Gen 1 (see the timeline above) and then VOS, built with Roman Slack. If I picked the bot up again I would load the speech models once at start-up so replies come back faster.

Stack
Python, discord.py, Ollama on my own laptop (llama3.2:1b, later dolphin3), Whisper medium.en, Parler-TTS mini v1. VOS Gen 1: Gemini API, Weaviate.
Status
Prototype
Period
February to September 2025 (the Discord bot is the first stage of VOS Gen 1)
Code
Not public
Why
An assistant I could talk to on the walk to class
Next stage
VOS and VibeOS, built with Roman Slack

← All work