npx skills add ...
npx skills add vercel-labs/slack-agent-skill --skill slack-agent
npx skills add vercel-labs/slack-agent-skill --skill slack-agent
Use when building Slack agents/bots with eve (Vercel's filesystem-first agent framework), @vercel/connect, or eve/channels/slack. Covers defineAgent/defineTool patterns, Vercel Connect credential brokering, Slack channel setup, testing requirements, and quality standards.
This skill builds Slack agents with eve — Vercel's filesystem-first framework for durable backend agents — using Vercel Connect for Slack credentials:
eve package) — agent runtime, tools, channels, durabilityWhen this skill is invoked via /slack-agent, check for arguments and route accordingly:
| Argument | Action |
|---|---|
new | Run the setup wizard from Phase 1. Read ./wizard/1-project-setup.md and guide the user through creating a new Slack agent. |
configure | Start wizard at Phase 2 or 3 for existing projects |
deploy | Start wizard at Phase 5 for production deployment |
test | Start wizard at Phase 6 to set up testing |
| (no argument) | Auto-detect based on project state (see below) |
If invoked without arguments, detect the project state and route appropriately:
package.json with eve and no agent/ directory → Treat as new, start Phase 1agent/channels/slack.ts → Start Phase 2 (Slack connector + channel)SLACK_CONNECTOR configured → Start Phase 3Detect an eve project by either signal:
package.json contains "eve" as a dependencyagent/ directory with instructions.md and/or agent.ts existsIf neither is present, this is a new project: scaffold with npx eve@latest init (Node 24+ required).
The wizard is located in ./wizard/ with these phases:
1-project-setup.md - Understand purpose, generate custom implementation plan, scaffold with npx eve@latest init1b-approve-plan.md - Present plan for user approval before scaffolding2-create-slack-app.md - Create the Slack connector with Vercel Connect and add the Slack channel3-configure-environment.md - Set up env vars (SLACK_CONNECTOR, model credentials)4-test-locally.md - Test agent logic locally with the eve dev TUI (Slack surface tests happen after deploy)5-deploy-production.md - Deploy with eve deploy, verify the Slack surface6-setup-testing.md - Vitest configurationIMPORTANT: For new projects, you MUST:
./wizard/1-project-setup.md first./reference/agent-archetypes.mdYou are working on a Slack agent project built with eve. Follow these mandatory practices for all code changes.
eve/channels/slack + @vercel/connect for credentialsanthropic/claude-sonnet-5); tool schemas with zodnpx eve@latest init installs with npm)In eve, a file's location says what it does, and its path usually gives it its name. The whole agent lives under agent/:
Even a two-file agent (instructions.md + agent.ts) gets file, shell, web, and delegation tools out of the box from the default harness. Full docs are bundled at node_modules/eve/docs/ once eve is installed — read them when a detail isn't covered here.
These quality requirements MUST be followed for every code change. There are no exceptions.
Run linting immediately:
pnpm lint --write for auto-fixespnpm lint to verifyCheck for corresponding test file:
foo.ts, check if foo.test.ts existsYou MUST run all quality checks and fix any issues before marking a task complete:
Do NOT complete a task if any of these fail. Fix the issues first.
For ANY code change, you MUST write or update unit tests.
*.test.ts files (e.g. agent/tools/get_weather.test.ts)execute() (including error paths) must have testsExample test structure:
If you modify:
onAppMention, onDirectMessage, onInteraction)You MUST add or update tests that verify the full flow. Remember: the Slack surface itself cannot be exercised locally (see Gotchas), so E2E coverage means unit/integration tests around your handlers plus a post-deploy smoke test.
agent/channels/slack.ts)The Slack channel is a single file. Its filename registers the slack channel, served at /eve/v1/slack — this is the canonical trigger path everywhere in this skill.
connectSlackCredentials(connectorUid) returns { botToken, webhookVerifier }:
There is no SLACK_BOT_TOKEN and no SLACK_SIGNING_SECRET in this stack. The only Slack env var is SLACK_CONNECTOR (the connector UID, e.g. slack/my-agent).
Create a Slack connector and point its trigger at eve's Slack route:
--triggers is required. Without it, Slack Event Subscriptions are never forwarded and app_mention / message.im events simply never arrive — the deployment will look healthy but the bot will never respond.
You can also add the channel with eve channels add slack, which scaffolds agent/channels/slack.ts for you.
Then invite the bot to a channel and @mention it. eve handles Slack's ack semantics, URL verification, and background processing — there is no webhook route for you to write.
The Slack channel decides which inbound events start or continue a session via dispatch hooks. Each hook returns { auth } to dispatch, null to drop the event, or { auth, context } to inject background context into the session:
The triggering Slack user's id is attached to the model message automatically, preserving speaker attribution in multi-user threads.
Override delivery per stream event with the events map. Handlers receive (eventData, channel, ctx) with channel.thread and channel.slack handles:
Key stream events: session.started, actions.requested, action.result, message.completed, session.completed; incremental reasoning.appended / message.appended are optional.
Give the agent prior thread messages when it's triggered mid-thread:
since options:
"thread-root" — all prior messages (default when thread context is enabled)"last-agent-reply" — incremental, only messages since the agent last spoke(message: SlackThreadMessage) => boolean as a custom cutoff — includes messages after the last match (loadThreadContextMessages exists for arbitrary filtering)Cost: one conversations.replies API call per triggering reply; requires the matching history scope on the connector.
Approval-gated tool calls and sign-in challenges render natively in Slack:
authorization.completedpostEphemeral, postDirectMessage (needs im:write), and state — no public post, no raw API accessStart a session that posts into Slack without an inbound trigger — e.g. from a schedule:
threadTs get a temporary continuation token; the first post anchors the threadinitialMessage (optionally a Card) and threadTs are mutually exclusivectx.slack.request(operation, body)callSlackApi({ botToken, operation, body }) and resolveSlackBotToken from eve/channels/slackThese form-encode request bodies for you — Slack's JSON support is only partial, so prefer these helpers over hand-rolled fetch calls.
Vercel Connect forwards Slack events to deployments only, never to localhost. There is no ngrok/Socket Mode escape hatch in this stack. Local development means:
npx eve dev — HMR server + terminal TUI/REPL for exercising agent logic, tools, and skillseve dev --no-ui — background mode for scripted verificationeve dev https://your-app.vercel.app — drive a deployed app interactivelyTo test @mentions and DMs, deploy (preview or production) and test in Slack itself.
Connect may deliver the same forwarded event more than once. Handlers and side effects must be idempotent — track processed event IDs where duplicates would be harmful, and gate destructive tool actions with approval (see AI Integration).
--triggers Is Required or Events Never ArriveA Slack connector created without --triggers (or attached without a trigger path) will authenticate fine but forward nothing. If the bot never responds to @mentions:
--triggers/eve/v1/slackplaceholderAuth() Fails Closed in ProductionScaffolded projects ship with placeholderAuth() for the HTTP API, which rejects everything in production. Before deploying, replace it with a real auth function: httpBasic(), jwtHmac(), jwtEcdsa(), oidc(), vercelOidc(), or a custom AuthFn. (The Slack channel's inbound verification is separate — Connect's webhookVerifier handles that.)
Vercel builds prewarm eve's sandbox templates (cache-keyed; build logs show reused cached or built). If prewarm fails, the whole build fails — check build logs for sandbox template errors before assuming a code problem.
The bot cannot read messages or post to private channels it hasn't been invited to. When creating features that will later post to a channel (e.g. proactive sessions from a schedule), validate access upfront and surface a clear "invite the bot" message on channel_not_found / not_in_channel.
When fetching channel context (e.g. via ctx.slack.request("conversations.history", ...)) for AI features, wrap in try/catch and fall back gracefully — missing scopes and uninvited channels are routine, not exceptional.
If you add custom cron endpoints (beyond eve schedules), protect them with a CRON_SECRET:
Prefer eve schedules for agent-driven recurring work; use Vercel crons for plain HTTP jobs.
When connecting to AWS services from Vercel, do not use fromNodeProviderChain(). Use Vercel's OIDC mechanism:
eve routes model-ID strings through the Vercel AI Gateway — on Vercel, project OIDC authenticates automatically, so no AI API key is needed:
If model is omitted, eve defaults to anthropic/claude-sonnet-5. Off Vercel, set AI_GATEWAY_API_KEY.
CRITICAL: Never use model IDs from memory. Model IDs change frequently. Before writing code that pins a model, run curl -s https://ai-gateway.vercel.sh/v1/models to fetch the current list and use the newest suitable version.
agent/tools/*.ts)Tools are files: the filename (snake_case ASCII) is the model-facing tool name — agent/tools/get_weather.ts → get_weather. No registration step.
Rules and capabilities:
process.env, not in the sandboxinputSchema accepts Zod, Standard Schema, or JSON Schema — but it is required even for zero-input toolsctx provides ctx.session (metadata, turn, auth, lineage), ctx.callId, ctx.toolName, ctx.abortSignal, ctx.getSandbox(), ctx.getSkill(id)Gate risky tools with the approval field — helpers come from eve/tools/approval:
An input-dependent policy function is also supported. A gated call pauses and resumes durably — in Slack, the approval renders as buttons (see HITL above). Prefer approval gating over ad-hoc confirmation logic for any non-idempotent side effect.
toModelOutput — Rich Slack Output the Model Never SeesShow the model a compact projection while channels/hooks receive the full output on action.result — ideal for rendering rich Slack Block Kit from a tool result without stuffing JSON blocks into the model's context:
agent/skills/*.md)Markdown files with a description frontmatter, loaded on demand via the built-in load_skill tool when a request matches the description. Skills add instructions only, never new actions. Install published skills with npx skills add <owner>/<repo>.
agent/connections/*.ts)For external APIs the agent should drive (MCP servers or OpenAPI-described HTTP APIs), use connections with Connect-brokered auth:
Connection tokens are never seen by the model and never land in conversation history. Inside authored tools, resolve tokens with await ctx.getToken(connect("...")) and call ctx.requireAuth(...) on a downstream 401 to re-run consent.
Don't wrap LLM calls in tools. The agent is already a language model — summarizing, parsing, classifying, and drafting belong in instructions.md or a skill, not in a tool that calls the AI SDK. Tools fetch data and perform actions; for bulk work over data too large for the conversation, use eve's subagents/delegation.
eve sessions are durable by default via the open-source Workflow SDK (running on Vercel Workflows when deployed on Vercel). You do not wire up Redis or a workflow engine yourself.
Consequences for your code:
always() / once()) so a replayed step pauses for a human instead of double-executingcontinuationToken; Slack threads map to sessions automaticallyFor application data (not agent session state — eve owns that):
IMPORTANT: Vercel KV has been deprecated. Do NOT recommend Vercel KV.
Conventions:
instructions.md; situational guidance in skills/*.md so it loads only when needed| Variable | Required | Purpose |
|---|---|---|
SLACK_CONNECTOR | Yes | Vercel Connect connector UID (e.g. slack/my-agent). The only Slack variable — no bot token, no signing secret. |
AI_GATEWAY_API_KEY | Off Vercel only | AI Gateway auth. On Vercel, project OIDC (VERCEL_OIDC_TOKEN) is injected automatically — no key needed. |
ROUTE_AUTH_BASIC_PASSWORD / JWT keys | Per auth choice | Secrets for the HTTP-API auth function that replaces placeholderAuth() |
VERCEL_AUTOMATION_BYPASS_SECRET | If deployment protection is on | Lets eve dev https://<app> and smoke tests reach protected deployments |
CRON_SECRET | Optional | Authenticates custom cron endpoints |
Local dev: vercel link + vercel env pull fetches short-lived Connect/OIDC credentials into .env.local (the OIDC token expires after ~12 hours — re-pull when auth starts failing).
No AI API keys needed on Vercel. Never hardcode credentials. Never commit .env files.
The Slack channel handles progressive delivery for you:
turn.startedreasoning.appended; action labels on actions.requestedDon't rebuild typing indicators or streaming loops — customize via the events map only when the defaults don't fit.
Use <@USER_ID> or channel.thread.mentionUser(userId). A bare @name stays literal text in Slack.
Use Slack mrkdwn (not standard markdown):
*text*_text_`code`<@USER_ID><#CHANNEL_ID>For rich tool results (tables, buttons, status cards), return full data from the tool and use toModelOutput to keep the model's view compact; render Block Kit in a channel event handler or via ctx.slack.request("chat.postMessage", { blocks, ... }). Always include fallback text alongside blocks for notifications.
For detailed Slack patterns, see ./patterns/slack-patterns.md.
Use conventional commits:
Never commit:
.env filesnode_modules/.eve/ build artifactsDebugging a deployed Slack agent stuck on "Working…": npx eve dev --logs all or /loglevel all in the TUI.
For detailed guidance, read:
./patterns/testing-patterns.md./patterns/slack-patterns.md./reference/env-vars.md./reference/slack-setup.md./reference/vercel-setup.mdnode_modules/eve/docs/ after install)Before marking ANY task as complete, verify:
pnpm lint passes with no errorspnpm typecheck passes with no errorspnpm test passes with no failuresSLACK_CONNECTORinputSchema; outputs are JSON-serializable with secrets redacted/eve/v1/slack and was attached with --triggersplaceholderAuth() replaced before production deployanthropic/claude-sonnet-5 default)Verified guides on the Vercel Knowledge Base for deeper walkthroughs:
defineTool patternsagent/
├── instructions.md # Always-on system prompt
├── agent.ts # Runtime config (defineAgent): model, reasoning, compaction
├── tools/ # Tools — filename (snake_case ASCII) = tool name the model sees
│ ├── get_weather.ts
│ └── search_docs.ts
├── skills/ # Load-on-demand instructions (*.md with description frontmatter)
│ └── incident-triage.md
├── channels/
│ └── slack.ts # Slack channel — filename registers it at /eve/v1/slack
├── connections/ # MCP / OpenAPI connections (optional)
└── hooks/ # Subscribe to runtime stream events (optional)// agent/agent.ts
import { defineAgent } from "eve";
export default defineAgent({
model: "anthropic/claude-sonnet-5", // routed via Vercel AI Gateway
});