Skip to main content
Guides

Slack Agent

Build a Slack bot that listens to channel messages, fetches weather, and replies through VoltOps Slack actions. Triggers deliver Slack events into your agent, and Actions let the agent send data back out. Follow the steps in order with your own keys and workspace.

Step 1 - Create the project

npm create voltagent-app@latest

Open the generated folder. If you skipped API key entry, add it to .env now (e.g. OPENAI_API_KEY=...).

Step 2 - Configure and start

If you skipped API key entry during setup, create or edit the .env file in your project root and add your API key:

OPENAI_API_KEY=your-api-key-here

Now start the development server:

npm run dev

You should see the VoltAgent server startup message:

═══════════════════════════════════════════════════
VOLTAGENT SERVER STARTED SUCCESSFULLY
═══════════════════════════════════════════════════
✓ HTTP Server: http://localhost:3141
↪ Share it: pnpm volt tunnel 3141 (secure HTTPS tunnel for teammates)
Docs: https://voltagent.dev/docs/deployment/local-tunnel/
✓ Swagger UI: http://localhost:3141/ui

Test your agents with VoltOps Console: https://console.voltagent.dev
═══════════════════════════════════════════════════

Step 3 - Set up the Slack trigger in VoltOps Console

  • Console → TriggersCreate Trigger (open console).
  • Choose Slack → Message posted to channel.
  • Use the managed VoltOps Slack app when prompted, and create a Slack credential. Keep the credentialId.

Step 4 - Expose your local agent with Volt Tunnel

pnpm volt tunnel 3141

Copy the tunnel URL and set it as the trigger destination in Console (Endpoint URL). See Local tunnel docs.

Step 5 - Update the agent (Slack trigger + weather)

Wire the Slack trigger and weather tool first:

import { openai } from "@ai-sdk/openai";
import { Agent, VoltAgent, createTriggers } from "@voltagent/core";
import { honoServer } from "@voltagent/server-hono";
import { weatherTool } from "./tools/weather";

type SlackMessagePayload = {
channel?: string;
thread_ts?: string;
ts?: string;
text?: string;
user?: string;
};

const logger = createPinoLogger({ name: "with-slack", level: "info" });

const slackAgent = new Agent({
name: "slack-agent",
instructions: "You are a Slack assistant.",
tools: [weatherTool],
model: openai("gpt-4o-mini"),
});

new VoltAgent({
agents: { slackAgent },
server: honoServer(),
logger,
triggers: createTriggers((on) => {
on.slack.messagePosted(async ({ payload, agents }) => {
const event = (payload as SlackMessagePayload | undefined) ?? {};
const channelId = event.channel;
const threadTs = event.thread_ts ?? event.ts;
const text = event.text ?? "";
const userId = event.user ?? "unknown-user";

if (!channelId || !text) {
logger.warn("Missing channel or text in Slack payload");
return;
}

await agents.slackAgent.generateText(`Slack channel: ${channelId}
Thread: ${threadTs ?? "new thread"}
User: <@${userId}>
Message: ${text}
If the user asks for weather, call getWeather.`);
});
}),
});

Weather tool (mock) reference:

import { createTool } from "@voltagent/core";
import { z } from "zod";

const weatherOutputSchema = z.object({
weather: z.object({
location: z.string(),
temperature: z.number(),
condition: z.string(),
humidity: z.number(),
windSpeed: z.number(),
}),
message: z.string(),
});

export const weatherTool = createTool({
name: "getWeather",
description: "Get the current weather for a specific location",
parameters: z.object({
location: z.string().describe("City or location to get weather for (e.g., San Francisco)"),
}),
outputSchema: weatherOutputSchema,
execute: async ({ location }) => {
// Mocked weather data for demo purposes; replace with a real API if desired.
const mockWeatherData = {
location,
temperature: Math.floor(Math.random() * 30) + 5,
condition: ["Sunny", "Cloudy", "Rainy", "Snowy", "Partly Cloudy"][
Math.floor(Math.random() * 5)
],
humidity: Math.floor(Math.random() * 60) + 30,
windSpeed: Math.floor(Math.random() * 30),
};

return {
weather: mockWeatherData,
message: `Current weather in ${location}: ${mockWeatherData.temperature}°C and ${mockWeatherData.condition.toLowerCase()} with ${mockWeatherData.humidity}% humidity and wind speed of ${mockWeatherData.windSpeed} km/h.`,
};
},
});

Step 6 - Add Slack action in VoltOps

Step 7 - Add sendSlackMessage to reply via VoltOps Actions

import { openai } from "@ai-sdk/openai";
import { Agent, VoltAgent, createTool, createTriggers } from "@voltagent/core";
import { VoltOpsClient } from "@voltagent/sdk";
import { createPinoLogger } from "@voltagent/logger";
import { honoServer } from "@voltagent/server-hono";
import { z } from "zod";
import { weatherTool } from "./tools/weather";

type SlackMessagePayload = {
channel?: string;
thread_ts?: string;
ts?: string;
text?: string;
user?: string;
};

const logger = createPinoLogger({ name: "with-slack", level: "info" });

const voltOps = new VoltOpsClient({
publicKey: process.env.VOLTAGENT_PUBLIC_KEY ?? "",
secretKey: process.env.VOLTAGENT_SECRET_KEY ?? "",
});

const sendSlackMessage = createTool({
name: "sendSlackMessage",
description: "Send a message to a Slack channel or thread via VoltOps.",
parameters: z.object({
channelId: z.string(),
text: z.string(),
threadTs: z.string().optional(),
}),
execute: async ({ channelId, text, threadTs }) => {
const credentialId = process.env.SLACK_CREDENTIAL_ID;
if (!credentialId) {
throw new Error("SLACK_CREDENTIAL_ID is not set");
}
return voltOps.actions.slack.postMessage({
credential: { credentialId },
channelId,
text,
threadTs,
linkNames: true,
});
},
});

const slackAgent = new Agent({
name: "slack-agent",
instructions: [
"You are a Slack assistant.",
"Use sendSlackMessage to reply in the same channel/thread.",
"Use getWeather for weather questions.",
].join(" "),
tools: [weatherTool, sendSlackMessage],
model: openai("gpt-4o-mini"),
});

new VoltAgent({
agents: { slackAgent },
server: honoServer(),
logger,
triggers: createTriggers((on) => {
on.slack.messagePosted(async ({ payload, agents }) => {
const event = (payload as SlackMessagePayload | undefined) ?? {};
const channelId = event.channel;
const threadTs = event.thread_ts ?? event.ts;
const text = event.text ?? "";
const userId = event.user ?? "unknown-user";

if (!channelId || !text) {
logger.warn("Missing channel or text in Slack payload");
return;
}

await agents.slackAgent.generateText(`Slack channel: ${channelId}
Thread: ${threadTs ?? "new thread"}
User: <@${userId}>
Message: ${text}
Respond in Slack via sendSlackMessage; use getWeather for weather questions.`);
});
}),
});

Ensure SLACK_CREDENTIAL_ID is set in .env.

Step 8 - Test end-to-end

  • Tunnel running, server running.
  • Mention the bot or post a message in the channel.
  • The agent should handle the Slack event, call getWeather when asked, and reply via sendSlackMessage.

Notes

  • Shared Slack app Request URL: https://api.voltagent.dev/hooks/slack (or your host).
  • Invite the bot to the channel (/invite @your-bot).
  • Keep VOLTAGENT_PUBLIC_KEY, VOLTAGENT_SECRET_KEY, and SLACK_CREDENTIAL_ID in .env.
  • Use Volt Tunnel locally; switch to your deployed URL later.
  • More on actions: Actions overview
  • More on triggers: Triggers usage

Table of Contents