<?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: Gourav Kumar Upadhyay</title>
    <description>The latest articles on DEV Community by Gourav Kumar Upadhyay (@poi5en).</description>
    <link>https://dev.to/poi5en</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%2F3977164%2Fe5eb07bc-baa8-470b-ad9b-109ef74fb7ec.png</url>
      <title>DEV Community: Gourav Kumar Upadhyay</title>
      <link>https://dev.to/poi5en</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/poi5en"/>
    <language>en</language>
    <item>
      <title>How to Build a Real-Time Slack Agent Using Bun, Hono, and Event-Driven Orchestration</title>
      <dc:creator>Gourav Kumar Upadhyay</dc:creator>
      <pubDate>Wed, 10 Jun 2026 07:34:44 +0000</pubDate>
      <link>https://dev.to/poi5en/how-to-build-a-real-time-slack-agent-using-bun-hono-and-event-driven-orchestration-3k40</link>
      <guid>https://dev.to/poi5en/how-to-build-a-real-time-slack-agent-using-bun-hono-and-event-driven-orchestration-3k40</guid>
      <description>&lt;p&gt;Building Slack bots is fun until you try to integrate heavy AI pipelines.&lt;/p&gt;

&lt;p&gt;If you are building an AI agent that runs sequential steps (e.g., Planning -&amp;gt; Web Searching -&amp;gt; Code Execution -&amp;gt; Summarization), the process is going to take 10 to 20 seconds.&lt;/p&gt;

&lt;p&gt;Here is the problem: Slack slash commands MUST receive an HTTP response within 3,000 milliseconds. If your server takes 3.1 seconds to respond, the user gets a generic "operation_timeout" error.&lt;/p&gt;

&lt;p&gt;In this post, I will share the exact architecture we used to bypass this timeout for our open-source agent platform, Saar Nexus, using Bun, Hono, and event-driven webhook streaming.&lt;/p&gt;

&lt;p&gt;The Timeout Architecture&lt;br&gt;
To run a long orchestration pipeline while keeping Slack happy, you cannot run the AI blocking-synchronously. Instead, the workflow must be divided into an instant acknowledgment and a decoupled background execution loop.&lt;/p&gt;

&lt;p&gt;[Slack User] ──(Slash Command)──&amp;gt; [Hono Server]&lt;br&gt;
                                       │&lt;br&gt;
                                (Verifies &amp;amp; ACKs &amp;lt;3s)&lt;br&gt;
                                       │&lt;br&gt;
                                       ▼ (Spawns Async Task)&lt;br&gt;
                                  [Agent Loop] &lt;br&gt;
                                       │&lt;br&gt;
                    (Streams updates to response_url in-place)&lt;br&gt;
                                       │&lt;br&gt;
                                       ▼&lt;br&gt;
                                [Slack Channel]&lt;/p&gt;

&lt;p&gt;Step 1: Return the Instant ACK&lt;br&gt;
When Slack hits our POST endpoint, we parse the signing headers and immediately return a 200 OK with a JSON payload telling the user that the request was received. This keeps the HTTP request under 150ms.&lt;/p&gt;

&lt;p&gt;Here is how we handle it in Hono:&lt;/p&gt;

&lt;p&gt;typescript&lt;/p&gt;

&lt;p&gt;router.post('/slack/command', async (c) =&amp;gt; {&lt;br&gt;
  const body = await c.req.parseBody();&lt;br&gt;
  const text = body.text;&lt;br&gt;
  const responseUrl = body.response_url;&lt;br&gt;
  // Acknowledge immediately to avoid Slack's 3-second timeout&lt;br&gt;
  const ackResponse = c.json({&lt;br&gt;
    response_type: 'ephemeral',&lt;br&gt;
    text: &lt;code&gt;🎯 Summoning Nexus agents for: "${text}"...&lt;/code&gt;,&lt;br&gt;
  });&lt;br&gt;
  // Trigger background task (Fire and Forget)&lt;br&gt;
  if (responseUrl) {&lt;br&gt;
    runOrchestrationInBackground(text, responseUrl);&lt;br&gt;
  }&lt;br&gt;
  return ackResponse;&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;Step 2: Stream Progress back to Slack (Bypassing the "Black Box")&lt;br&gt;
Once the command is acknowledged, the server triggers the background task. But if the user has to wait 20 seconds for a response, they might think the server crashed.&lt;/p&gt;

&lt;p&gt;To solve this, we stream progress updates back to Slack's webhook (response_url). Slack allows you to send up to 5 updates to this URL within 30 minutes.&lt;/p&gt;

&lt;p&gt;To keep the UX clean, we do not post multiple new messages (which would spam the channel). Instead, we send a JSON payload with replace_original: true. This overwrites the existing message in-place:&lt;/p&gt;

&lt;p&gt;typescript&lt;/p&gt;

&lt;p&gt;async function runOrchestrationInBackground(prompt: string, responseUrl: string) {&lt;br&gt;
  try {&lt;br&gt;
    let statusText = "🤔 Triage agent is planning implementation...";&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Send first status update (replace original thinking message)
await postToSlack(responseUrl, `🎯 *Request:* "${prompt}"\n\n${statusText}`);
// Stream generator events from our multi-agent runner
for await (const event of orchestrate(prompt)) {
  if (event.type === 'plan') {
    statusText = `📝 *Created plan!* Running specialist agents...`;
  } else if (event.type === 'merging') {
    statusText = `🔀 Merging findings into final response...`;
  } else if (event.type === 'merged') {
    // Final formatted response (converts standard markdown to Slack mrkdwn)
    const formatted = formatSlackResponse(event.mergedBrief);
    await postToSlack(responseUrl, formatted);
    return; // Finished!
  }
  // Update progress in-place
  await postToSlack(responseUrl, `🎯 *Request:* "${prompt}"\n\n${statusText}`);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} catch (err) {&lt;br&gt;
    await postToSlack(responseUrl, &lt;code&gt;❌ Something went wrong: ${err.message}&lt;/code&gt;);&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
async function postToSlack(url: string, text: string) {&lt;br&gt;
  await fetch(url, {&lt;br&gt;
    method: 'POST',&lt;br&gt;
    headers: { 'Content-Type': 'application/json' },&lt;br&gt;
    body: JSON.stringify({&lt;br&gt;
      response_type: 'in_channel',&lt;br&gt;
      replace_original: true,&lt;br&gt;
      text: text&lt;br&gt;
    })&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Step 3: Throttling for Rate Limits&lt;br&gt;
Notice that we do not post on every single token or event. Because Slack limits us to 5 updates per URL, we only update on 4 key milestones:&lt;/p&gt;

&lt;p&gt;Initial command received (ACK)&lt;br&gt;
Orchestrator plan generated&lt;br&gt;
Merge step started&lt;br&gt;
Final completed response&lt;br&gt;
This keeps us safely below the limit, ensuring your bots never throw 404 used_url errors.&lt;/p&gt;

&lt;p&gt;Persisting configuration locally&lt;br&gt;
Since the Slack integration supports multi-workspace connections, we store bot tokens dynamically. To ensure this survives VPS deployments, we configure Docker volume mapping to persist our SQLite database:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  docker-compose.yml
&lt;/h1&gt;

&lt;p&gt;services:&lt;br&gt;
  server:&lt;br&gt;
    build: ./server&lt;br&gt;
    volumes:&lt;br&gt;
      - ./server/nexus.db:/app/nexus.db&lt;/p&gt;

&lt;p&gt;Try it out!&lt;br&gt;
Nexus is fully open-source (MIT), built on Bun + Hono + React, and ready to self-host. Check out the repository, run a local deploy, and try the Slack integration:&lt;/p&gt;

&lt;p&gt;⭐ GitHub: &lt;a href="https://github.com/Poi5eN/Nexus" rel="noopener noreferrer"&gt;https://github.com/Poi5eN/Nexus&lt;/a&gt; &lt;br&gt;
🎯 Live Demo: &lt;a href="https://saarlabs.in" rel="noopener noreferrer"&gt;https://saarlabs.in&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
