본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 10. 16:40

Bun, Hono, 그리고 이벤트 기반 오케스트레이션(Event-Driven Orchestration)을 사용하여 실시간 Slack 에이전트

요약

Slack의 3초 타임아웃 제한을 극복하기 위해 Bun, Hono를 활용한 이벤트 기반 오케스트레이션 아키텍처를 제안합니다. 즉각적인 HTTP 응답(ACK) 후 백그라운드에서 AI 에이전트 루프를 실행하고, response_url을 통해 진행 상황을 스트리밍하는 방식을 설명합니다.

핵심 포인트

  • Slack의 3,000ms 타임아웃 제한 해결 방법 제시
  • Bun과 Hono를 이용한 고성능 비동기 처리 아키텍처
  • 즉각적인 ACK 반환과 분리된 백그라운드 실행 루프 구현
  • response_url을 활용한 실시간 진행 상황 스트리밍

Slack 봇을 만드는 것은 무거운 AI 파이프라인(AI pipelines)을 통합하려고 하기 전까지는 즐거운 일입니다.

만약 여러분이 순차적인 단계(예: 계획(Planning) -> 웹 검색(Web Searching) -> 코드 실행(Code Execution) -> 요약(Summarization))를 실행하는 AI 에이전트를 구축하고 있다면, 그 과정은 10초에서 20초 정도 소요될 것입니다.

여기에 문제가 있습니다: Slack 슬래시 커맨드(Slash commands)는 반드시 3,000밀리초(ms) 이내에 HTTP 응답을 받아야 합니다. 만약 서버가 응답하는 데 3.1초가 걸린다면, 사용자는 일반적인 "operation_timeout" 에러를 받게 됩니다.

이 포스트에서 저는 Bun, Hono, 그리고 이벤트 기반 웹훅 스트리밍(event-driven webhook streaming)을 사용하여 우리의 오픈 소스 에이전트 플랫폼인 Saar Nexus를 위해 이 타임아웃을 우회하기 위해 사용한 정확한 아키텍처를 공유하겠습니다.

타임아웃 아키텍처 (The Timeout Architecture)
Slack을 만족시키면서 긴 오케스트레이션 파이프라인(orchestration pipeline)을 실행하려면, AI를 동기식(blocking-synchronously)으로 실행해서는 안 됩니다. 대신, 워크플로우는 즉각적인 승인(instant acknowledgment)과 분리된 백그라운드 실행 루프(decoupled background execution loop)로 나뉘어야 합니다.

[Slack User] ──(Slash Command)──> [Hono Server]

(Verifies & ACKs <3s)

▼ (Spawns Async Task)
[Agent Loop]

(Streams updates to response_url in-place)


[Slack Channel]

1단계: 즉각적인 ACK 반환
Slack이 우리의 POST 엔드포인트(endpoint)를 호출하면, 우리는 서명 헤더(signing headers)를 파싱하고 요청이 수신되었음을 사용자에게 알리는 JSON 페이로드와 함께 즉시 200 OK를 반환합니다. 이를 통해 HTTP 요청을 150ms 미만으로 유지합니다.

Hono에서 이를 처리하는 방법은 다음과 같습니다:

typescript

router.post('/slack/command', async (c) => {
const body = await c.req.parseBody();
const text = body.text;
const responseUrl = body.response_url;
// Slack의 3초 타임아웃을 피하기 위해 즉시 승인(Acknowledge)
const ackResponse = c.json({
response_type: 'ephemeral',
text: 🎯 Summoning Nexus agents for: "${text}"...,
});
// 백그라운드 작업 트리거 (Fire and Forget)
if (responseUrl) {
runOrchestrationInBackground(text, responseUrl);
}
return ackResponse;
});

2단계: Slack으로 진행 상황 스트리밍하기 ("블랙박스" 우회하기)
명령이 승인(acknowledge)되면 서버는 백그라운드 작업을 트리거합니다. 하지만 사용자가 응답을 받기 위해 20초 동안 기다려야 한다면, 서버가 다운되었다고 생각할 수 있습니다.

이를 해결하기 위해, 우리는 진행 상황 업데이트를 Slack의 웹훅(webhook, response_url)으로 스트리밍합니다. Slack은 30분 이내에 이 URL로 최대 5개의 업데이트를 보낼 수 있도록 허용합니다.

사용자 경험(UX)을 깔끔하게 유지하기 위해, 우리는 여러 개의 새로운 메시지를 게시하지 않습니다(채널에 스팸을 유발할 수 있기 때문입니다). 대신, replace_original: true가 포함된 JSON 페이로드(payload)를 전송합니다. 이는 기존 메시지를 제자리에서 덮어씁니다:

typescript

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

// 첫 번째 상태 업데이트 전송 (기존의 생각 중이라는 메시지를 교체)
await postToSlack(responseUrl, `🎯 *Request:* "${prompt}"\n\n${statusText}`);
// 멀티 에이전트 러너(multi-agent runner)로부터 제너레이터 이벤트(generator events)를 스트리밍
...

} catch (err) {
await postToSlack(responseUrl, ❌ Something went wrong: ${err.message});
}
}

async function postToSlack(url: string, text: string) {
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
response_type: 'in_channel',
replace_original: true,
text: text
})
});
}

3단계: 속도 제한(Rate Limits)을 위한 스로틀링(Throttling)
우리가 모든 토큰(token)이나 이벤트(event)마다 게시하지 않는다는 점에 주목하세요. Slack은 URL당 5개의 업데이트로 제한하기 때문에, 우리는 다음 4가지 주요 마일스톤(milestone)에서만 업데이트를 수행합니다:

  1. 초기 명령 수신 (ACK)
  2. 오케스트레이터(Orchestrator) 계획 생성 완료
  3. 병합(Merge) 단계 시작
  4. 최종 완료 응답

이렇게 하면 제한 범위 내에서 안전하게 유지되어, 봇이 404 used_url 에러를 발생시키지 않도록 보장합니다.

설정 로컬 저장하기
Slack 통합은 멀티 워크스페이스(multi-workspace) 연결을 지원하므로, 봇 토큰(bot tokens)을 동적으로 저장합니다. 이것이 VPS 배포 환경에서도 유지되도록 하기 위해, SQLite 데이터베이스를 영구적으로 보관할 수 있도록 Docker 볼륨 매핑(volume mapping)을 구성합니다:

yaml

docker-compose.yml

services:
server:
build: ./server
volumes:
- ./server/nexus.db:/app/nexus.db

직접 시도해 보세요!
Nexus는 Bun + Hono + React를 기반으로 구축된 완전한 오픈 소스(MIT) 프로젝트이며, 셀프 호스팅(self-host)할 준비가 되어 있습니다. 저장소(repository)를 확인하고, 로컬 배포(local deploy)를 실행하여 Slack 연동을 테스트해 보세요:

⭐ GitHub: https://github.com/Poi5eN/Nexus
🎯 Live Demo: https://saarlabs.in

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0