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.
The parts
Section titled “The parts”| Part | What it does | More |
|---|---|---|
| Gateway handlers | Store 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 |
| Backfill | Reads old history once, then catches up after every restart. | Messages and backfill |
| Commands, menus and buttons | Answer what members run. Replies are private to the member who ran them. | Commands |
| Reminder engine | Wakes 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 scheduler | Writes project summaries and personal interest digests with Gemini and sends them as DMs. Off unless SUMMARIES_ENABLED is true. | Summaries |
| Ask | Answers /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 runtime | Checks 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 runtime | Reads 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 notes | Joins 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 sweep | Turns stored messages into vectors through Gemini. | AI and embeddings |
| Gemini | Reads 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 route | One HTTP route, GET /health. | Scripts and health |
| Linear | Gives 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 Postgres | Holds 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.
How the bot talks to you
Section titled “How the bot talks to you”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 nowand/ask), the message menu, buttons and the ticket form. Only the member who acted can see them. A public/askposts 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.
What starts, in order
Section titled “What starts, in order”-
Load and check the environment. A missing or invalid variable stops startup, listing every bad variable.
-
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.
-
Build the services and log whether Gemini is configured. The Linear client is built only if
LINEAR_API_KEYis set. Build the embedding runtime, which stays idle without a Gemini key. -
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).
-
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.
-
Decide which commands exist:
- Always:
/whoami,/hours,/follow,/reminders,/reminder-rulesand the message menu. /ask: whenASK_ENABLEDistrueand Gemini is configured./summary,/interestsand/summary-admin: whenSUMMARIES_ENABLEDistrue./linear-admin: whenLINEAR_API_KEYis set./notesand/notes-admin: when voice notes are on./notes-adminalone 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.
- Always:
-
Build the ticket prompt runtime. It checks messages only when
TICKET_PROMPTS_ENABLEDistrueand bothGEMINI_API_KEYandLINEAR_API_KEYare set. -
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.
-
Build the reminder engine and the summary scheduler. Register the interaction handler.
-
Start the embedding runtime. Its first sweep waits 60 seconds so it does not compete with the first backfill pages.
-
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.
-
Open the HTTP port.
-
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.
-
Log in to Discord. When it reports ready, member sync, backfill and slash command registration all start.
-
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_DAYSdays and ask records older thanASK_LOG_RETENTION_DAYSdays, 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. -
The bot logs one line per feature (
[ask],[reminders],[summaries],[tickets],[voice],[meetings]):disabled,off: needs ...naming the missing key or switch, orenabledwith 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. -
When voice notes are on, the bot finishes any voice meeting a restart interrupted, once, and logs
[voice] recovery: N finished, M failedwhen 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.
What is optional
Section titled “What is optional”| Part | Switch | Without it |
|---|---|---|
| Reminders | REMINDERS_ENABLED, default true | The 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 testers | REMINDER_ALLOWLIST, default empty | Everyone 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 interests | SUMMARIES_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. |
| Ask | ASK_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 prompts | TICKET_PROMPTS_ENABLED, default false, plus GEMINI_API_KEY and LINEAR_API_KEY | No 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 notes | VOICE_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 transcripts | MEETING_TRANSCRIPTS_ENABLED, default false, and GEMINI_API_KEY. LINEAR_API_KEY is optional | No 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. |
| Embeddings | EMBEDDINGS_ENABLED, default true, and GEMINI_API_KEY | No queue, no sweep and no embedding calls. Messages are still stored, and search matches by words only. |
| Linear | LINEAR_API_KEY | Summaries 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.
What it keeps, and for how long
Section titled “What it keeps, and for how long”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.
| What | Kept | Removed when |
|---|---|---|
| Messages | Forever, with the author, channel and times | A deleted message keeps its row but loses its text. Edits replace the text. |
| Embeddings of messages | Until the message changes | The 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 answer | The 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 text | 30 days | The 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 team | Until the prompt ends | Created, 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 names | MEETING_TRANSCRIPT_RETENTION_DAYS days, default 90 | The transcript row is deleted, at every start and then daily. The summary, attendees, action items and suggested team stay. |
| Voice audio | Never written to disk | Held in memory until each piece is turned into text. |
| Interests | Until the member removes them | Removal deletes the row. |
| Members, reminder choices and runs, summary and meeting settings | Forever | A member who leaves is marked as left. Nothing is deleted. |
Shutting down
Section titled “Shutting down”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.
- Messages and backfill for what the bot keeps.
- The reminder engine for when a DM is sent.
- Summaries for how a summary is built and sent.
- Ticket prompts for when the bot offers to file a ticket.
- Meeting transcripts for how an uploaded transcript becomes action items.
- Voice notes for how a voice call becomes the same.