<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Curtis Summers</title>
    <description>The latest articles on DEV Community by Curtis Summers (@curtissummers).</description>
    <link>https://dev.to/curtissummers</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3762363%2F886fc90d-ed24-48b7-bafa-1fd2e8e2c445.png</url>
      <title>DEV Community: Curtis Summers</title>
      <link>https://dev.to/curtissummers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/curtissummers"/>
    <language>en</language>
    <item>
      <title>Build AI Agents with Hot Dev</title>
      <dc:creator>Curtis Summers</dc:creator>
      <pubDate>Sat, 23 May 2026 11:47:54 +0000</pubDate>
      <link>https://dev.to/curtissummers/build-ai-agents-with-hot-dev-3po6</link>
      <guid>https://dev.to/curtissummers/build-ai-agents-with-hot-dev-3po6</guid>
      <description>&lt;p&gt;Learn how to build AI chat agents with two kinds of memory: one that follows a user across devices, and one shared by everyone in a channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hot Chat&lt;/strong&gt; is a web chat demo you can clone and start in about 15 minutes, with two AI agents side by side: a &lt;strong&gt;Personal Mode&lt;/strong&gt; agent whose memory is keyed to the user, and a &lt;strong&gt;Team Mode&lt;/strong&gt; agent whose memory is keyed to the channel.&lt;/p&gt;

&lt;p&gt;The UI is a Next.js + TypeScript app that talks to Hot through &lt;a href="https://www.npmjs.com/package/@hot-dev/sdk" rel="noopener noreferrer"&gt;&lt;code&gt;@hot-dev/sdk&lt;/code&gt;&lt;/a&gt;. The agent layer is built on &lt;a href="https://hot.dev/pkg/hot.dev/hot-ai-agent" rel="noopener noreferrer"&gt;&lt;code&gt;hot.dev/hot-ai-agent&lt;/code&gt;&lt;/a&gt;, a reusable Hot package for transports, commands, runtime stores, rendering, streaming, and MCP helpers. Hot Dev itself is &lt;a href="https://github.com/hot-dev/hot" rel="noopener noreferrer"&gt;open source under Apache 2.0&lt;/a&gt;, so everything you see in this post runs on code you can read.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/WubNrFsbC5U"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Before running the demo, &lt;a href="https://hot.dev/docs/getting-started" rel="noopener noreferrer"&gt;install Hot&lt;/a&gt; if you don't already have the &lt;code&gt;hot&lt;/code&gt; CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hot-dev/hot-demos
&lt;span class="nb"&gt;cd &lt;/span&gt;hot-demos/hot-chat
hot dev &lt;span class="nt"&gt;--open&lt;/span&gt;                  &lt;span class="c"&gt;# terminal 1: both agents&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env            &lt;span class="c"&gt;# terminal 2: the UI&lt;/span&gt;
&lt;span class="c"&gt;# Hot App -&amp;gt; API Keys -&amp;gt; New Key; paste it into HOT_API_KEY.&lt;/span&gt;
&lt;span class="c"&gt;# Then add your ANTHROPIC_API_KEY (https://console.anthropic.com/) to .env.&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;. The toolbar switches between the two agents live, no restart needed.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; to get the real, streamed, memory-grounded replies the demo is built around. Without it the UI still loads, but assistant replies fall back to a stub that just says the LLM is disabled. The harness sits on &lt;a href="https://hot.dev/pkg/hot.dev/hot-ai" rel="noopener noreferrer"&gt;&lt;code&gt;hot-ai&lt;/code&gt;&lt;/a&gt;, so you can wire a different provider in your own app.&lt;/p&gt;

&lt;p&gt;The full walkthrough is at &lt;a href="https://hot.dev/docs/demos/hot-chat" rel="noopener noreferrer"&gt;hot.dev/docs/demos/hot-chat&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Agents, One Project
&lt;/h2&gt;

&lt;p&gt;Hot Chat ships two agents in one Hot project. They look nearly identical on the surface: same chat UI, same slash commands, same streaming replies.&lt;/p&gt;

&lt;p&gt;The difference is how each one scopes memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Personal Mode&lt;/strong&gt; is identity-first. Whatever you tell the agent follows &lt;em&gt;you&lt;/em&gt; across sessions, tabs, and devices. Type &lt;code&gt;/remember I prefer launch updates that start with blockers&lt;/code&gt;, close the tab, come back tomorrow on a different device, ask &lt;code&gt;/recall&lt;/code&gt;, and the same notes are still there.&lt;/p&gt;

&lt;p&gt;This is the pattern for assistants, journaling apps, per-user copilots, and anything where memory belongs to the person rather than the conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team Mode&lt;/strong&gt; is session-first. Memory is keyed to the channel, so two people chatting in the same room share one view, and two channels stay independent. Type &lt;em&gt;"we decided to ship docs before launch"&lt;/em&gt;, then &lt;em&gt;"CI is the only blocker"&lt;/em&gt;, then &lt;code&gt;/ask what is blocking launch?&lt;/code&gt;, and the reply cites the matching records with attribution.&lt;/p&gt;

&lt;p&gt;This is the pattern for team chat bots, support inboxes, and shared workspaces.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Team Mode&lt;/th&gt;
&lt;th&gt;Personal Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Session&lt;/td&gt;
&lt;td&gt;the channel or thread&lt;/td&gt;
&lt;td&gt;a scratch context per person&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity&lt;/td&gt;
&lt;td&gt;the person who posted&lt;/td&gt;
&lt;td&gt;the durable memory owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;scoped to the session&lt;/td&gt;
&lt;td&gt;scoped to the user&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6e3v01l30a39lh40sz95.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6e3v01l30a39lh40sz95.png" alt="The Hot Chat web UI showing a streaming reply from the Team Mode agent with the Personal/Team mode toggle visible in the toolbar" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hot Chat, mid-conversation. The toolbar switches between Personal and Team mode live.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inside the UI
&lt;/h2&gt;

&lt;p&gt;The Hot Chat UI is intentionally generic. It looks like a chat product, not a framework demo. That's because the experience is the point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quick-prompt chips&lt;/strong&gt; help you explore each mode without learning a syntax first. Try &lt;em&gt;Recall preferences&lt;/em&gt;, &lt;em&gt;Daily brief&lt;/em&gt;, &lt;em&gt;Decisions&lt;/em&gt;, or &lt;em&gt;Ask the team&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming replies&lt;/strong&gt; render as the agent generates them. Slash-command replies stream too, identically to LLM responses, so the UI doesn't have to know which path produced the message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File attachments&lt;/strong&gt; let you drag in a small &lt;code&gt;notes.md&lt;/code&gt; file or screenshot. The agent stores the file name and type as metadata and could be extended to parse contents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity controls&lt;/strong&gt; show the exact &lt;code&gt;session_id&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt; the agent receives, in the same format a Slack or Telegram adapter would generate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Graph&lt;/strong&gt; in the Hot Dev App shows each slash command as its own typed event handler, so you can see the agent structure without digging through a central dispatch function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fad84lzotft6rgg7dam0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fad84lzotft6rgg7dam0b.png" alt="The Hot Dev Agent Graph view for the PersonalAgent demo showing each slash command as its own event handler node connected to the streaming reply outputs" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One event handler per command, no central dispatch.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;hot-ai-agent&lt;/code&gt; Brings
&lt;/h2&gt;

&lt;p&gt;If you've built an AI chat agent before, you've probably written some version of this stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a slash-command parser&lt;/li&gt;
&lt;li&gt;a way to thread LLM calls through retrieval-augmented memory&lt;/li&gt;
&lt;li&gt;a streaming reply mechanism&lt;/li&gt;
&lt;li&gt;per-agent stores for state and stats&lt;/li&gt;
&lt;li&gt;per-request session and identity bindings so tools know who's talking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most chat agents end up reinventing these pieces.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hot-ai-agent&lt;/code&gt; extracts that layer. Concretely, it gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed transport messages&lt;/strong&gt;: a single &lt;code&gt;IncomingMessage&lt;/code&gt; shape that adapters for web, Slack, Telegram, or anything else can translate into. The agent never branches on transport.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slash-command parsing&lt;/strong&gt;: &lt;code&gt;/ask@MyBot what's up?&lt;/code&gt; becomes &lt;code&gt;{name: "ask", arg: "what's up?"}&lt;/code&gt;, with the Telegram-style &lt;code&gt;@MyBot&lt;/code&gt; suffix stripped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The memory-grounded chat turn&lt;/strong&gt;: the canonical &lt;code&gt;recall -&amp;gt; persist user -&amp;gt; bind request -&amp;gt; stream -&amp;gt; persist assistant&lt;/code&gt; lifecycle in one function call. The order matters; getting it wrong can cause the user's fresh message to contaminate their own retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable streaming events&lt;/strong&gt;: every agent emits &lt;code&gt;&amp;lt;agent&amp;gt;:reply:start&lt;/code&gt;, &lt;code&gt;:delta&lt;/code&gt;, and &lt;code&gt;:end&lt;/code&gt; events at a stable, agent-scoped label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-request session binding&lt;/strong&gt;: when an LLM tool runs mid-turn, the resolved session and identity are bound to the current agent request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-agent stores and a session registry&lt;/strong&gt;: each agent gets state, stats, errors, and a notification ledger. Scheduled jobs can fan out per session with error isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP plumbing&lt;/strong&gt;: expose any agent function as an MCP tool with one annotation, so Claude Desktop, Cursor, and other MCP clients can call into the agent directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it deliberately doesn't include: transport vendor packages. No Slack, Telegram, or Discord packages are baked in. Those live in the application and translate to the neutral types, which keeps the harness portable and the dependency tree small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the Hood, in One Snippet
&lt;/h2&gt;

&lt;p&gt;When all the harness pieces are in place, an entire chat-style event handler in Hot looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;remember-message
meta {
    agent: PersonalAgent,
    on-event: "personal-agent:remember",
}
fn (event) {
    d        event.data
    sender   identity-from-data(d)
    session  session-from-data(d, sender)
    input    base-input(d, session, sender, Str(or(d.text, "")))
    ::chat-turn/run-chat-turn(turn-cfg, input)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole handler. Resolve who's talking, package up the input, and hand off to &lt;code&gt;run-chat-turn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;RAG, persistence, ordering, streaming, request binding, tool dispatch, and error handling sit behind that one call.&lt;/p&gt;

&lt;p&gt;Adding a new slash command in your own agent follows the same pattern: one more function, one more &lt;code&gt;on-event&lt;/code&gt; annotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Go Next
&lt;/h2&gt;

&lt;p&gt;Clone Hot Chat and try swapping the LLM provider, adding a slash command, or wiring a second adapter on top of the same agent. Everything below is open source and free to read.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run Hot Chat:&lt;/strong&gt; &lt;a href="https://hot.dev/docs/demos/hot-chat" rel="noopener noreferrer"&gt;hot.dev/docs/demos/hot-chat&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot Chat source:&lt;/strong&gt; &lt;a href="https://github.com/hot-dev/hot-demos/tree/main/hot-chat" rel="noopener noreferrer"&gt;github.com/hot-dev/hot-demos/tree/main/hot-chat&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hot-ai-agent&lt;/code&gt; package:&lt;/strong&gt; &lt;a href="https://hot.dev/pkg/hot.dev/hot-ai-agent" rel="noopener noreferrer"&gt;hot.dev/pkg/hot.dev/hot-ai-agent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hot-ai&lt;/code&gt; package:&lt;/strong&gt; &lt;a href="https://hot.dev/pkg/hot.dev/hot-ai" rel="noopener noreferrer"&gt;hot.dev/pkg/hot.dev/hot-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@hot-dev/sdk&lt;/code&gt; for JS/TS:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@hot-dev/sdk" rel="noopener noreferrer"&gt;npmjs.com/package/@hot-dev/sdk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot Dev on GitHub (Apache 2.0):&lt;/strong&gt; &lt;a href="https://github.com/hot-dev/hot" rel="noopener noreferrer"&gt;github.com/hot-dev/hot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow Hot Dev:&lt;/strong&gt; &lt;a href="https://x.com/hotdotdev" rel="noopener noreferrer"&gt;@hotdotdev on X&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>agents</category>
    </item>
    <item>
      <title>How to Build an AI Slack Bot with Claude, GPT, Grok, &amp; Gemini Using Hot Dev</title>
      <dc:creator>Curtis Summers</dc:creator>
      <pubDate>Thu, 12 Feb 2026 18:30:35 +0000</pubDate>
      <link>https://dev.to/curtissummers/how-to-build-an-ai-slack-bot-with-claude-gpt-grok-gemini-using-hot-dev-91k</link>
      <guid>https://dev.to/curtissummers/how-to-build-an-ai-slack-bot-with-claude-gpt-grok-gemini-using-hot-dev-91k</guid>
      <description>&lt;p&gt;What if you could build a Slack bot that talks to Claude, GPT, Grok, &lt;em&gt;and&lt;/em&gt; Gemini — and switch between them with a single chat command? And what if the whole thing fit in a single file?&lt;/p&gt;

&lt;p&gt;That's what we're building today. No framework. No boilerplate. Just &lt;a href="https://hot.dev" rel="noopener noreferrer"&gt;Hot Dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj2ife008xyypyqdf0li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj2ife008xyypyqdf0li.png" alt="The bot responding in Slack with an AI reply" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full source code:&lt;/strong&gt; &lt;a href="https://github.com/hot-dev/hot-demos/tree/main/slack-bot" rel="noopener noreferrer"&gt;github.com/hot-dev/hot-demos/tree/main/slack-bot&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;An AI-powered Slack bot that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Talks to four AI providers&lt;/strong&gt; — Claude, GPT, Grok, and Gemini&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switches models live&lt;/strong&gt; — Type &lt;code&gt;!ai gpt&lt;/code&gt; in the channel and the next reply comes from GPT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads conversation context&lt;/strong&gt; — The bot knows what was said recently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploys with one command&lt;/strong&gt; — &lt;code&gt;hot deploy&lt;/code&gt; to go live on Hot Dev Cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll get this running locally first, then deploy it to production with real-time webhooks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hot Dev CLI&lt;/strong&gt; — Download from &lt;a href="https://hot.dev/download" rel="noopener noreferrer"&gt;hot.dev/download&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code extension&lt;/strong&gt; (optional) — Search "Hot" by &lt;code&gt;hot-dev&lt;/code&gt; in the Extensions panel for syntax highlighting, autocomplete, and error checking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Slack workspace&lt;/strong&gt; where you can create apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At least one AI API key&lt;/strong&gt; — Anthropic (Claude) is the default, but OpenAI, xAI, or Google Gemini work too&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create a Slack App
&lt;/h2&gt;

&lt;p&gt;First, we need a Slack app with the right permissions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://api.slack.com/apps" rel="noopener noreferrer"&gt;api.slack.com/apps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create New App&lt;/strong&gt; → &lt;strong&gt;From scratch&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name (e.g., "Hot AI Bot") and select your workspace&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe89m63871urlp4magu5q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe89m63871urlp4magu5q.png" alt="Create New App dialog at api.slack.com" width="521" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, add the bot permissions. Go to &lt;strong&gt;OAuth &amp;amp; Permissions&lt;/strong&gt; and add these &lt;strong&gt;Bot Token Scopes&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;channels:history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read messages from public channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;channels:read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get channel info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chat:write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Post messages and replies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;groups:history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read messages from private channels &lt;em&gt;(optional)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;im:history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read direct messages &lt;em&gt;(optional)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjtkffb8y68romn5i36t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjtkffb8y68romn5i36t.png" alt="Bot Token Scopes in the Slack app settings" width="688" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now install the app to your workspace:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Install App&lt;/strong&gt; in the sidebar&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Install to Workspace&lt;/strong&gt; and approve&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;Bot User OAuth Token&lt;/strong&gt; — it starts with &lt;code&gt;xoxb-&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, invite the bot to a channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/invite @HotAIBot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;strong&gt;Channel ID&lt;/strong&gt; — you can find it by right-clicking the channel name → "View channel details" → the ID is at the bottom (starts with &lt;code&gt;C&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Set Up the Project
&lt;/h2&gt;

&lt;p&gt;Clone the demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hot-dev/hot-demos.git
&lt;span class="nb"&gt;cd &lt;/span&gt;hot-demos/slack-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project configuration lives in &lt;code&gt;hot.hot&lt;/code&gt;. Here's the key part — the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hot.project.slack-bot.deps {
    "hot.dev/hot-ai": "1.0.0",
    "hot.dev/slack": "1.0.4",
    "hot.dev/anthropic": "1.0.3",
    "hot.dev/openai": "1.0.4",
    "hot.dev/xai": "1.0.3",
    "hot.dev/gemini": "1.0.3"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are &lt;a href="https://hot.dev/pkg" rel="noopener noreferrer"&gt;Hot Dev packages&lt;/a&gt; — first-class integrations for Slack and all four AI providers. You don't need to install them separately. Hot Dev resolves them automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Understand the Bot Code
&lt;/h2&gt;

&lt;p&gt;The entire bot lives in one file: &lt;code&gt;hot/src/slack-bot/bot.hot&lt;/code&gt;. Let's look at the key parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Imports
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;::slack-bot::bot ns

// Namespace aliases — short names for the packages we use
::channels  ::slack::channels
::messaging ::slack::messaging
::misc      ::slack::misc
::webhooks  ::slack::webhooks
// ... other aliases (see full code on GitHub)

// AI provider aliases
::anthropic-chat ::anthropic::messages
::openai-chat    ::openai::chat
::xai-chat       ::xai::responses
::gemini-chat    ::gemini::chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're new to Hot: &lt;code&gt;::&lt;/code&gt; denotes a namespace path. &lt;code&gt;::channels ::slack::channels&lt;/code&gt; creates a short alias so we can write &lt;code&gt;::channels/conversations-history(...)&lt;/code&gt; instead of the full &lt;code&gt;::slack::channels/conversations-history(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;import&lt;/code&gt; keyword, no curly braces — just &lt;code&gt;alias full-namespace-path&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Model Selection
&lt;/h3&gt;

&lt;p&gt;The bot lets users switch AI models live in the Slack channel with &lt;code&gt;!ai&lt;/code&gt; commands. Here's how that's configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MODEL_ALIASES {
  "claude":   {service: "Anthropic", model: "claude-sonnet-4-5"},
  "opus":     {service: "Anthropic", model: "claude-opus-4-6"},
  "gpt":      {service: "OpenAi",    model: "gpt-5.2"},
  "grok":     {service: "Xai",       model: "grok-4-1-fast"},
  "gemini":   {service: "Gemini",    model: "gemini-3-flash-preview"}
  // ... plus shorthand aliases (see full code on GitHub)
}

DEFAULT_SELECTION {service: "Anthropic", model: "claude-sonnet-4-5"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: in Hot, assignment uses a space, not &lt;code&gt;=&lt;/code&gt;. &lt;code&gt;MODEL_ALIASES {…}&lt;/code&gt; means "bind the name &lt;code&gt;MODEL_ALIASES&lt;/code&gt; to this map." This is one of Hot's core syntax rules — no equals sign.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Dispatch
&lt;/h3&gt;

&lt;p&gt;This is the cleanest part. Hot's &lt;code&gt;match&lt;/code&gt; flow dispatches to the right AI provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AiService enum { Anthropic, OpenAi, Xai, Gemini }

ask-ai fn match (service: AiService, model: Str, message: Str, system: Str): Str {
  AiService.Anthropic =&amp;gt; { ::anthropic-chat/chat(model, message, system) }
  AiService.OpenAi    =&amp;gt; { ::openai-chat/chat(model, message, system) }
  AiService.Xai       =&amp;gt; { ::xai-chat/chat(model, message, system) }
  AiService.Gemini    =&amp;gt; { ::gemini-chat/chat(model, message, system) }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four AI providers, four lines. Each Hot Dev AI package exposes the same &lt;code&gt;chat(model, message, system)&lt;/code&gt; interface, so switching providers is just matching on the enum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Messages
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;handle-message&lt;/code&gt; function does the heavy lifting. It checks for &lt;code&gt;!ai&lt;/code&gt; commands first, then falls through to the AI reply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;handle-message fn (channel: Str, message: Map, bot-user-id: Str): Map {
  text or(message.text, "")
  cmd parse-ai-command(text)

  cond {
    // !ai — show current model and available options
    eq(cmd, "help") =&amp;gt; {
      // ... format and post model list (see full code on GitHub)
    }

    // !ai &amp;lt;selection&amp;gt; — acknowledge the switch
    is-some(cmd) =&amp;gt; {
      svc get(SERVICE_INFO, cmd.service)
      ::messaging/chat-post-message(::messaging/ChatPostMessageRequest({
        channel: channel,
        text: `${svc.emoji} Switched to *${svc.name}* \`${cmd.model}\``
      }))
    }

    // Regular message — detect model from history and reply with context
    =&amp;gt; {
      sel detect-selection(channel, bot-user-id)
      service to-service(sel.service)
      context fetch-context(channel, bot-user-id)
      prompt if(is-empty(context),
        text,
        `Recent conversation:\n\n${context}\n\n---\nRespond to the latest message.`
      )

      ai-response ask-ai(service, sel.model, prompt, SYSTEM_PROMPT)
      ::messaging/chat-post-message(::messaging/ChatPostMessageRequest({
        channel: channel,
        text: ai-response
      }))
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cond&lt;/code&gt; flow is Hot's branching construct — it evaluates conditions top-to-bottom and takes the first match. The &lt;code&gt;=&amp;gt;&lt;/code&gt; with no condition at the end is the default branch.&lt;/p&gt;

&lt;p&gt;One clever detail: &lt;code&gt;detect-selection&lt;/code&gt; scans the last 100 messages in the channel for the most recent &lt;code&gt;!ai&lt;/code&gt; command. The model selection is stored in the chat history itself — no database, no state file, no Redis. Just Slack messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Polling for Messages
&lt;/h3&gt;

&lt;p&gt;For local development, the bot polls the channel on a schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check-channel-poll
meta {
  schedule: "every 15 seconds",  // comment out scheduled polling in Hot Dev Cloud in favor of webhooks + Slack Events API
  on-event: "slack-bot:check"
}
fn (event) {
  channel get-channel-id()
  bot-user-id get-bot-user-id()
  // ... fetch and filter messages (see full code on GitHub)
  for-each(new-messages, (msg) { handle-message(channel, msg, bot-user-id) })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;meta&lt;/code&gt; block is how Hot attaches metadata to functions. Here it says: "Run this every 15 seconds, and also run it when someone sends the &lt;code&gt;slack-bot:check&lt;/code&gt; event." You can also trigger it manually at any time with &lt;code&gt;hot eval 'send("slack-bot:check")'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One thing you might notice: there are no &lt;code&gt;println&lt;/code&gt; or logging statements anywhere in the code. That's because Hot Dev lets you inspect every function call, its arguments, return values, and timing — all from the app. No manual logging needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix674epvae13x1qoy76m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix674epvae13x1qoy76m.png" alt="Hot Dev function call trace" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's all the code you need to understand for now. Let's run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Run Locally
&lt;/h2&gt;

&lt;p&gt;Start the dev server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hot dev &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts the Hot Dev runtime locally and opens the app in your browser at &lt;code&gt;http://localhost:4680&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wreti0pwar3gsday5bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wreti0pwar3gsday5bi.png" alt="Terminal showing hot dev output" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjrm80ys75qcsfiyczzg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjrm80ys75qcsfiyczzg.png" alt="The local Hot Dev app in the browser" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Context Variables
&lt;/h3&gt;

&lt;p&gt;Hot Dev uses &lt;strong&gt;context variables&lt;/strong&gt; for configuration and secrets. In the app, go to &lt;strong&gt;Context Variables&lt;/strong&gt; and set the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slack.api.key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Bot User OAuth Token (&lt;code&gt;xoxb-...&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slack.channel.id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The channel ID (&lt;code&gt;C...&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anthropic.api.key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Anthropic API key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're using a different AI provider as your default, set that provider's key instead (e.g., &lt;code&gt;openai.api.key&lt;/code&gt;, &lt;code&gt;xai.api.key&lt;/code&gt;, or &lt;code&gt;gemini.api.key&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3lrzrkhlo1uatbck2gj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3lrzrkhlo1uatbck2gj.png" alt="Context Variables page with API keys configured" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test It
&lt;/h3&gt;

&lt;p&gt;The bot will check the channel every 15 seconds. To trigger a check immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hot &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s1"&gt;'send("slack-bot:check")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type a message in your Slack channel and wait for the bot to reply.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5p8moq0aajcb3bxkgaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5p8moq0aajcb3bxkgaf.png" alt="The bot replying to a message in Slack" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Switch AI Models
&lt;/h3&gt;

&lt;p&gt;Type &lt;code&gt;!ai&lt;/code&gt; in the Slack channel to see the current model and all options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot replies with the active model and a list of available providers. To switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!ai gpt          → GPT-5.2
!ai grok         → Grok 4.1 Fast
!ai gemini       → Gemini 3 Flash
!ai claude       → Claude Sonnet 4.5
!ai opus         → Claude Opus 4.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The selection sticks — the bot scans the channel history for the most recent &lt;code&gt;!ai&lt;/code&gt; command and uses that model for all replies. No config change, no restart. Just type a command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jm21jpqkqevmjw4tnol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jm21jpqkqevmjw4tnol.png" alt="The !ai command showing available models and switching to GPT" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Taking It to Production
&lt;/h2&gt;

&lt;p&gt;At this point you have a working AI Slack bot. But there's a catch: it's polling every 15 seconds. That's fast enough for testing, but not ideal for production — it makes unnecessary API calls when no one is talking, and there's still a small delay.&lt;/p&gt;

&lt;p&gt;The fix is &lt;strong&gt;webhooks&lt;/strong&gt;. Instead of polling, Slack sends your bot a message the instant someone types in the channel. The response is immediate. The reason we can't use webhooks during local development is simple: Slack's Events API needs a public URL to send events to, and it can't reach &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To use webhooks, you need to deploy your bot to &lt;a href="https://hot.dev" rel="noopener noreferrer"&gt;Hot Dev Cloud&lt;/a&gt;. Hot Dev gives your bot a public URL and handles scaling — all with a single command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign Up and Get an API Key
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create an account at &lt;a href="https://app.hot.dev" rel="noopener noreferrer"&gt;app.hot.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;API Keys&lt;/strong&gt; and create a new key&lt;/li&gt;
&lt;li&gt;Set it in your terminal or in a &lt;code&gt;.env&lt;/code&gt; file in your project root:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HOT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8yvobiji6o2i9v82gki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8yvobiji6o2i9v82gki.png" alt="The API Keys page in the Hot Dev App" width="797" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Context Variables
&lt;/h3&gt;

&lt;p&gt;Same as local — go to &lt;a href="https://app.hot.dev/contexts" rel="noopener noreferrer"&gt;Context Variables&lt;/a&gt; in the Hot Dev App and set the same keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;slack.api.key&lt;/code&gt; — your bot token&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slack.channel.id&lt;/code&gt; — the channel ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slack.signing.secret&lt;/code&gt; — your Signing Secret (find it under &lt;strong&gt;Basic Information&lt;/strong&gt; in your Slack app — needed for webhook verification)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;anthropic.api.key&lt;/code&gt; — your AI provider key(s)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foiy5wtvpk2tripw41hfe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foiy5wtvpk2tripw41hfe.png" alt="Context Variables in the Hot Dev App" width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Comment Out Polling
&lt;/h3&gt;

&lt;p&gt;Since webhooks handle messages in real time, you don't need the polling schedule in production. Comment it out in &lt;code&gt;bot.hot&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check-channel-poll
meta {
  // schedule: "every 15 seconds",  // comment out scheduled polling in Hot Dev Cloud in favor of webhooks + Slack Events API
  on-event: "slack-bot:check"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function still exists — you can trigger it manually with &lt;code&gt;hot eval&lt;/code&gt; if you ever need to — but it won't run on a schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hot deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ hot deploy
No build ID provided, creating new bundle build from current source...
Discovering namespaces in: hot/src
Found namespace ::slack-bot::bot with 11 functions, 1 types
Discovered 1 namespaces
  ### package doc gen lines omitted ###
  Inserted 5 event handler(s)
  Inserted 1 webhook(s)
✓ Created bundle build 019c51a7-a5b9-7450-8025-43f5787d8bc1
  Size: 229013 bytes
✓ Successfully uploaded build 019c51a7-a5b9-7450-8025-43f5787d8bc1
✓ Successfully deployed build 019c51a7-a5b9-7450-8025-43f5787d8bc1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your bot is live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Slack Webhooks
&lt;/h3&gt;

&lt;p&gt;Now tell Slack to send events to your bot in real time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Slack app → &lt;strong&gt;Event Subscriptions&lt;/strong&gt; → toggle &lt;strong&gt;Enable Events&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Request URL&lt;/strong&gt; to your webhook endpoint — find the webhook URL at &lt;a href="https://app.hot.dev/webhooks" rel="noopener noreferrer"&gt;Hot Dev App Webhooks&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gfx6v7t5rhvnfv3uqzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gfx6v7t5rhvnfv3uqzj.png" alt="The Webhooks page in the Hot Dev App" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Subscribe to &lt;strong&gt;Bot Events&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;message.channels&lt;/code&gt; — messages in public channels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message.groups&lt;/code&gt; — messages in private channels &lt;em&gt;(optional — requires &lt;code&gt;groups:history&lt;/code&gt; scope)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message.im&lt;/code&gt; — direct messages to the bot &lt;em&gt;(optional — requires &lt;code&gt;im:history&lt;/code&gt; scope)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save Changes&lt;/strong&gt; — Slack will prompt you to reinstall the app to pick up the new event permissions.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Subscribing to an event locks its required scope — you won't be able to remove the scope until you remove the event subscription first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwjto8amg1gfpl9ybzcx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwjto8amg1gfpl9ybzcx.png" alt="Slack Event Subscriptions page with webhook URL and bot events" width="800" height="807"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Webhook Handler Works
&lt;/h3&gt;

&lt;p&gt;The bot already has the webhook code — it just wasn't doing anything during local development. Here's what kicks in when deployed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on-slack-event
meta {
  webhook: {
    service: "slack-bot",
    path: "/events",
    method: "POST",
    description: "Receive Slack Events API callbacks"
  }
}
fn (request: HttpRequest): HttpResponse {
  cond {
    // Slack URL verification challenge (one-time setup)
    eq(request.body.type, "url_verification") =&amp;gt; {
      HttpResponse({status: 200, headers: {"content-type": "application/json"},
        body: {challenge: request.body.challenge}})
    }

    // Verify the request signature
    not(::webhooks/verify-request(request)) =&amp;gt; {
      HttpResponse({status: 401, body: {error: "invalid signature"}})
    }

    // Handle the event — top-level messages only (simplified — see full code on GitHub)
    eq(request.body.type, "event_callback") =&amp;gt; {
      event request.body.event
      cond {
        and(eq(event.type, "message"), is-null(event.subtype), is-null(event.bot_id), is-null(event.thread_ts)) =&amp;gt; {
          bot-user-id get-bot-user-id()
          handle-message(or(event.channel, get-channel-id()), event, bot-user-id)
        }
      }
      HttpResponse({status: 200, body: {ok: true}})
    }

    =&amp;gt; { HttpResponse({status: 200, body: {ok: true}}) }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;meta { webhook: {...} }&lt;/code&gt; block tells Hot Dev to register this function as an HTTP endpoint. In production, it gets a public URL automatically. The handler verifies Slack's request signature before processing — this prevents unauthorized requests.&lt;/p&gt;

&lt;p&gt;Since you commented out the polling schedule, only the webhook handler is active in production.&lt;/p&gt;

&lt;p&gt;Here's the full picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Dev (polling):                    Production (webhooks):

  Schedule (every 15s)                     Slack Events API
         │                                       │
         ▼                                       ▼
  check-channel-poll()               on-slack-event(request)
         │                                       │
         ├─ fetch recent messages                ├─ verify signature
         ├─ filter bot/system msgs               ├─ filter to top-level msgs
         └─ for each message:                    └─ handle-message()
              └─ handle-message()                      │
                       │                               ├─ !ai command? → switch
                       ├─ !ai command? → switch model  └─ regular msg? → ask AI → reply
                       └─ regular msg? → ask AI → reply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both sides call the same &lt;code&gt;handle-message()&lt;/code&gt; function. The only difference is how messages arrive.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I use this with a private channel?&lt;/strong&gt;&lt;br&gt;
Yes. Add the &lt;code&gt;groups:history&lt;/code&gt; and &lt;code&gt;groups:read&lt;/code&gt; scopes to your Slack app and invite the bot to the private channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I change the default AI model?&lt;/strong&gt;&lt;br&gt;
Edit the &lt;code&gt;DEFAULT_SELECTION&lt;/code&gt; line in &lt;code&gt;bot.hot&lt;/code&gt;. Set &lt;code&gt;service&lt;/code&gt; to one of &lt;code&gt;"Anthropic"&lt;/code&gt;, &lt;code&gt;"OpenAi"&lt;/code&gt;, &lt;code&gt;"Xai"&lt;/code&gt;, or &lt;code&gt;"Gemini"&lt;/code&gt; and &lt;code&gt;model&lt;/code&gt; to the model name you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if my AI API key is missing?&lt;/strong&gt;&lt;br&gt;
The bot will return an error for that provider. It won't crash — Hot's error handling propagates the failure as a Result. The other providers still work fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Hot Dev free to use?&lt;/strong&gt;&lt;br&gt;
Hot Dev is free for local development. See &lt;a href="https://hot.dev/pricing" rel="noopener noreferrer"&gt;hot.dev/pricing&lt;/a&gt; for cloud deployment options.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Install Hot Dev and try it yourself: &lt;a href="https://hot.dev/download" rel="noopener noreferrer"&gt;hot.dev/download&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hot-dev/hot-demos/tree/main/slack-bot" rel="noopener noreferrer"&gt;Full demo source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hot.dev/docs" rel="noopener noreferrer"&gt;Hot Dev documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hot.dev/docs/language" rel="noopener noreferrer"&gt;Hot Language Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/hotdotdev" rel="noopener noreferrer"&gt;Hot Dev on X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built something cool with Hot Dev? Share it with us on &lt;a href="https://x.com/hotdotdev" rel="noopener noreferrer"&gt;X @hotdotdev&lt;/a&gt; — we'd love to see it.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What would you build with Hot Dev? Drop a comment — I'd love to hear what integrations you'd tackle first.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>slack</category>
      <category>ai</category>
      <category>chatbot</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
