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
- Anthropic
- Google Gemini
- Groq
- Mistral
OPENAI_API_KEY=your-api-key-here
ANTHROPIC_API_KEY=your-api-key-here
GOOGLE_GENERATIVE_AI_API_KEY=your-api-key-here
GROQ_API_KEY=your-api-key-here
MISTRAL_API_KEY=your-api-key-here
Now start the development server:
- npm
- yarn
- pnpm
npm run dev
yarn dev
pnpm 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 → Triggers → Create 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
- Console → Actions → Create Action (open console) (see Actions overview)
- Choose Slack and the same credential.
- Save.
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
getWeatherwhen asked, and reply viasendSlackMessage.
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, andSLACK_CREDENTIAL_IDin.env. - Use Volt Tunnel locally; switch to your deployed URL later.
- More on actions: Actions overview
- More on triggers: Triggers usage