Skip to content

The big picture

The bot is one program. This page shows its parts, what talks to what, what starts in which order and which parts you can switch off.

Terms used on this page:

  • Gateway: the live connection to Discord that delivers events such as new messages.
  • Backfill: reading a channel’s old history, which the gateway does not replay.
  • DM: a direct message.
  • Runtime: the part of the program that runs one feature.
  • Tick: one wake-up of a repeating timer.
  • Gemini: Google’s AI service, which the bot calls for summaries, answers and more.
  • Embedding: a list of numbers that stands for the meaning of a message, so search can match by meaning. Stored as a vector in Postgres, using its pgvector extension.
  • Neon: the hosted Postgres database service the bot uses.
  • Linear: the ticket tracker the team plans work in.
  • Admin: a member with the Manage Server permission.
Discord connects to one bot process holding the gateway handlers, commands, reminder engine, summary scheduler, ticket prompt runtime, meeting transcript runtime, voice notes runtime, embedding sweep and health route. The process connects to Neon Postgres and, optionally, to Gemini and Linear.DiscordBot process (Node)Gateway handlers and backfillCommands, menus and buttonsReminder engineSummary schedulerTicket prompt runtimeEmbedding queue and sweepMeeting transcript runtimeVoice notes runtimeHealth routeNeonPostgresGemini(optional)Linear(optional)
PartWhat it doesMore
Gateway handlersStore messages and channels as they happen, keep the member list current, and read a tag of the bot to see whether it is a follow-up request, a question or neither. Each new stored message is also offered to the ticket prompt runtime.Messages and backfill
BackfillReads old history once, then catches up after every restart.Messages and backfill
Commands, menus and buttonsAnswer what members run. Replies are private to the member who ran them.Commands
Reminder engineWakes on a tick to find members who owe a post and sends DMs. Follow-ups have their own timer and go out to the second.The reminder engine
Summary schedulerWrites project summaries and personal interest digests with Gemini and sends them as DMs. Off unless SUMMARIES_ENABLED is true.Summaries
AskAnswers /ask and a tag that is a question. Searches the stored messages and the meetings the asker can see with Gemini tools, and cites its sources. No timer. Off unless ASK_ENABLED is true and GEMINI_API_KEY is set.Asking questions
Ticket prompt runtimeChecks each new message with Gemini and Linear, and may DM the author an offer to file a ticket. Pressing Create ticket opens a prefilled form. No timer. Off unless TICKET_PROMPTS_ENABLED is true.Ticket prompts
Meeting transcript runtimeReads a file uploaded to the meetings channel with Gemini, posts a summary and, with Linear connected, DMs each owner their action items. Pressing Create selected files tickets, or sets the status of or comments on an existing ticket for an update item. No timer. Off unless MEETING_TRANSCRIPTS_ENABLED is true.Meeting transcripts
Voice notesJoins a voice channel only when a member in it runs /notes start or uses Discord’s Invite to voice on the bot, transcribes each speaker with the PlaySafe transcription hosts and hands the text to the meeting transcript runtime. No timer except a maximum meeting length. Off unless VOICE_NOTES_ENABLED is true, with meeting transcripts on.Voice notes
Embedding queue and sweepTurns stored messages into vectors through Gemini.AI and embeddings
GeminiReads tags, writes summaries, answers questions, classifies feature ideas, reads transcripts and makes the embeddings. Optional: each feature that needs it says so.AI and embeddings
Health routeOne HTTP route, GET /health.Scripts and health
LinearGives summaries ticket context, lets ticket prompts look for similar tickets, creates tickets when a member submits a form or an owner presses Create selected, and links tickets in a meeting summary. For an update item it sets a status or comments on an existing ticket, never Done or Canceled. It also supplies the members’ teams and the labels, and /linear-admin links members to Linear users. Optional.Summaries, Ticket prompts
Neon PostgresHolds everything the bot remembers. Needs the pgvector extension.Data

The bot serves one Discord server, the one named by DISCORD_GUILD_ID. Messages, channel events and member events from any other server are ignored, and so are messages you type to the bot in a DM. The one exception is a voice channel invite link sent as a DM while voice notes are on. Menus and buttons on the bot’s own DMs still work.

Three ways, and no others:

  • DMs it starts. Channel reminders, follow-ups, the one-time setup DM, scheduled summaries, ticket prompts and meeting action items. When a voice meeting ends by itself, whoever started it gets the result. A tag for a follow-up with no time gets a DM with a time picker.
  • Private replies. Answers to commands (including /summary now and /ask), the message menu, buttons and the ticket form. Only the member who acted can see them. A public /ask posts its answer in the channel, but its refusals and errors stay private.
  • Replies in a channel. A short reply to a tag that asks for a follow-up, or the answer itself as a public reply to your message when the tag is a question. It reacts with an hourglass instead when you already have a question running, or when it told you about a limit in the last hour. It stays silent on any other tag. In the meetings channel it replies to an upload with a status, the summary and a note about DMs that did not arrive. With voice notes on, it also posts in a voice channel’s text chat: a notice when it joins and the result when it stops. The summary of a voice meeting goes to the meetings channel or to that chat.
  1. Load and check the environment. A missing or invalid variable stops startup, listing every bad variable.

  2. Open the database connection pool on the pooled URL (Neon’s shared connection endpoint), and start opening three connections in the background so the first ticket form does not wait for one.

  3. Build the services and log whether Gemini is configured. The Linear client is built only if LINEAR_API_KEY is set. Build the embedding runtime, which stays idle without a Gemini key.

  4. Create the Discord client with the Guilds, Guild Messages, Message Content, Guild Members, Guild Voice States and Direct Messages intents (the kinds of events it asks Discord to send).

  5. When meeting transcripts are on and Gemini is configured, build the meeting transcript runtime with a listener for the meetings channel. When voice notes are on too, build the voice runtime, with listeners for voice state changes and for voice channel invites sent as DMs.

  6. Decide which commands exist:

    • Always: /whoami, /hours, /follow, /reminders, /reminder-rules and the message menu.
    • /ask: when ASK_ENABLED is true and Gemini is configured.
    • /summary, /interests and /summary-admin: when SUMMARIES_ENABLED is true.
    • /linear-admin: when LINEAR_API_KEY is set.
    • /notes and /notes-admin: when voice notes are on. /notes-admin alone when only meeting transcripts are on.

    The follow-up, ticket, ask and meeting button handlers are always registered, so messages already sent keep working when a feature is switched off.

  7. Build the ticket prompt runtime. It checks messages only when TICKET_PROMPTS_ENABLED is true and both GEMINI_API_KEY and LINEAR_API_KEY are set.

  8. Register the message, member sync and bot-tag handlers. The message handler offers each newly stored message to the ticket prompt runtime, and tells it about deletes. After the first full member sync, when Linear is connected, the bot matches members to Linear users by name. A failure is logged and ignored.

  9. Build the reminder engine and the summary scheduler. Register the interaction handler.

  10. Start the embedding runtime. Its first sweep waits 60 seconds so it does not compete with the first backfill pages.

  11. Arrange for slash command registration and backfill to run when Discord reports ready. Backfill also runs whenever the bot starts a new session after losing its connection.

  12. Open the HTTP port.

  13. When meeting transcripts are on, read the summary channel name from the database. If the read fails, startup stops, because the bot could post to the wrong channel on the default name.

  14. Log in to Discord. When it reports ready, member sync, backfill and slash command registration all start.

  15. Once registration succeeds, the bot clears stored summary text older than 30 days, stored ticket drafts older than 30 days, stored transcript text older than MEETING_TRANSCRIPT_RETENTION_DAYS days and ask records older than ASK_LOG_RETENTION_DAYS days, even with those features off. The transcript and ask cleanups repeat every 24 hours, and so does the summary one while the scheduler runs. A failed cleanup is logged and startup carries on.

  16. The bot logs one line per feature ([ask], [reminders], [summaries], [tickets], [voice], [meetings]): disabled, off: needs ... naming the missing key or switch, or enabled with the limits. The reminder engine and the summary scheduler start when enabled, and each runs a tick at once. Ticket prompts have no timer, because they react to new messages. The bot does not check for the meetings channel.

  17. When voice notes are on, the bot finishes any voice meeting a restart interrupted, once, and logs [voice] recovery: N finished, M failed when there was one. It also sweeps again about a minute after an AI outage pauses uploads.

If command registration fails, the process exits and neither reminders nor summaries start.

PartSwitchWithout it
RemindersREMINDERS_ENABLED, default trueThe engine does not start: no channel reminders, follow-up DMs or setup DMs. Commands still work. /follow still saves follow-ups, sent once reminders are back on. A tag no longer sets a follow-up, but a tag that is a question is still answered when asking is on.
Limit to testersREMINDER_ALLOWLIST, default emptyEveryone is eligible. When set, it limits every DM the bot starts on its own: reminders, follow-ups, setup DMs, scheduled summaries, interest digests, ticket prompts, meeting action items and the result of a voice meeting that ended by itself. It does not limit /summary now, /interests now, /ask or a tag that is a question, and it does not decide who may upload a transcript or start voice notes.
Summaries and interestsSUMMARIES_ENABLED, default false, and GEMINI_API_KEY/summary, /interests and /summary-admin do not exist and nothing is sent. With the switch on but no key, the commands exist, /summary now says AI is not configured and the scheduler idles. See Summaries.
AskASK_ENABLED, default false, and GEMINI_API_KEY/ask does not exist and a tag that is a question gets no answer. Thumbs buttons on answers already sent still work. Stored question records are still pruned. See Asking questions.
Ticket promptsTICKET_PROMPTS_ENABLED, default false, plus GEMINI_API_KEY and LINEAR_API_KEYNo message is checked and no prompt is sent. Prompts already sent keep working while LINEAR_API_KEY is set. Without that key every ticket button answers Ticket prompts are turned off. See Ticket prompts.
Voice notesVOICE_NOTES_ENABLED, default false, with MEETING_TRANSCRIPTS_ENABLED and GEMINI_API_KEY/notes does not exist and the bot never joins a voice channel. /notes-admin still exists while meeting transcripts are on. Uploads still work. See Voice notes.
Meeting transcriptsMEETING_TRANSCRIPTS_ENABLED, default false, and GEMINI_API_KEY. LINEAR_API_KEY is optionalNo upload is read and no channel is created, and voice notes are off with it. Direct messages already sent keep working while LINEAR_API_KEY is set. Without Linear, summaries post with no ticket links and no action item message is sent. See Meeting transcripts.
EmbeddingsEMBEDDINGS_ENABLED, default true, and GEMINI_API_KEYNo queue, no sweep and no embedding calls. Messages are still stored, and search matches by words only.
LinearLINEAR_API_KEYSummaries lose ticket context, ticket prompts are off, meeting summaries lose their ticket links, no action item message is sent, no ticket can be created, and /linear-admin and the name matching of members are gone. /summary-admin create and map-channel refuse. Summaries and channel mappings made while Linear was connected still work from channel messages.

Every variable, with ranges, is on Configuration.

Everything the bot remembers is in the database, and only some of it expires. What the bot stores explains each feature’s data in plain words, and Data lists the tables.

WhatKeptRemoved when
MessagesForever, with the author, channel and timesA deleted message keeps its row but loses its text. Edits replace the text.
Embeddings of messagesUntil the message changesThe message is deleted or edited to have no text. An edit makes a new one.
Question records (/ask and tags)ASK_LOG_RETENTION_DAYS days, default 30. Question, search arguments and ids, never the answerThe whole row is deleted after that. If the tag message or its channel is deleted, the question text and trace are blanked at once.
Summary text30 daysThe text is cleared and the record of the run stays. At every start and about daily while the scheduler runs.
Ticket drafts, including the suggested teamUntil the prompt endsCreated, dismissed or failed, source message or channel deleted, or older than 30 days at the next start. The rest of the row stays.
Meeting transcript text and speech-to-text model namesMEETING_TRANSCRIPT_RETENTION_DAYS days, default 90The transcript row is deleted, at every start and then daily. The summary, attendees, action items and suggested team stay.
Voice audioNever written to diskHeld in memory until each piece is turned into text.
InterestsUntil the member removes themRemoval deletes the row.
Members, reminder choices and runs, summary and meeting settingsForeverA member who leaves is marked as left. Nothing is deleted.

When the host asks the bot to stop (the SIGTERM or SIGINT signal), it first saves a running voice meeting (up to 8 seconds), which the next start finishes. It then stops the reminder engine, summary scheduler, embedding runtime, ticket prompt runtime and meeting transcript runtime, and aborts backfill:

  • Ticket checks and uploads waiting in line are dropped. One already running gets up to 5 seconds before it is aborted, and an upload is marked failed with reason aborted.
  • A summary run stops before its next group or recipient. Summaries it did not reach are marked failed with reason shutdown.

It waits up to 10 seconds for the rest, then closes the HTTP server, the Discord client and the database pool. A hard exit fires at 25 seconds. The container is given 30 seconds to stop.