Build Your First Durable AI Agent with Vercel Eve (Beginner's Guide)

A beginner-friendly guide to Vercel Eve. Scaffold a durable AI agent as plain files, add a tool, connect it to Slack and Discord, swap in open source models, then deploy on Vercel or your own VPS with Dokploy.

Build Your First Durable AI Agent with Vercel Eve (Beginner's Guide)

Vercel just dropped Eve, and the easiest way to describe it is “Next.js, but for agents.” Instead of one giant config object you have to keep in your head, every part of your agent gets a file. Instructions live in one file. Tools live in a folder. Channels (Slack, Discord, a web UI) live in another. Eve reads that folder structure and turns it into a running agent that works locally, serves HTTP, and keeps a conversation alive across many turns.

I’ve built agents with a few different frameworks, and the thing that won me over here is how little ceremony there is. You can read a whole Eve project by looking at the directory tree. That’s it. No registry to keep in sync, no wiring file that drifts out of date.

In this guide we’ll scaffold an agent from scratch, give it a tool, connect it to both Slack and Discord, swap the default Claude model for an open source one, and then deploy it, either on Vercel or on your own VPS with Dokploy. By the end you’ll have a working assistant your team can talk to.

Eve is in beta

Eve is currently in beta under the Vercel beta terms, so APIs and behavior can change before general availability. Pin a version in package.json if you’re building something you care about, and re-check the docs when you upgrade.

What Eve actually is

Eve is a framework for building durable agents as ordinary files in a TypeScript project. “Durable” is the word that matters. An Eve session isn’t one request and one response. It can stream progress while it works, call tools and subagents, pause to ask a human for approval, resume after the answer arrives, and keep state across turns. Under the hood it leans on the open source Workflow SDK to make sessions resumable and crash-safe, so your code can focus on the work instead of the plumbing.

Here’s what a small project looks like:

my-agent/
├── package.json
└── agent/
    ├── agent.ts          # picks the model, runtime config
    ├── instructions.md   # who the agent is, how it behaves
    ├── tools/            # typed functions the model can call
    │   └── get_weather.ts
    ├── skills/           # longer procedures, loaded on demand
    │   └── plan_a_trip.md
    └── channels/         # Slack, Discord, web, etc.
        └── slack.ts

A file’s location says what it does, and its path usually gives it a name. Drop agent/tools/get_weather.ts in place and Eve discovers a tool called get_weather. Rename the file and the tool name moves with it. That’s the whole mental model.

  • instructions.md tells the agent who it is and how to behave (the always-on system prompt)
  • agent.ts chooses the model and sets runtime options
  • tools/ holds typed functions the model can call
  • skills/ holds longer procedures the model only loads when they’re useful
  • channels/ connect the agent to HTTP clients, Slack, Discord, and anywhere else people talk to it

Start with just instructions.md and agent.ts. Add the other folders when you actually need them.

Prerequisites

Before you scaffold anything, make sure you have:

  • Node 24 or newer (Eve pins a modern Node runtime)
  • npm, which ships with Node
  • A model credential: either a Vercel AI Gateway key (AI_GATEWAY_API_KEY), a linked Vercel project for OIDC, or a direct provider key like ANTHROPIC_API_KEY

The scaffold defaults to anthropic/claude-sonnet-4.6 routed through the Vercel AI Gateway. If you skip the credential, the dev terminal flags it and walks you through pasting a key with its /model command, so you won’t get stuck.

Step 1: Scaffold your first agent

npx can run eve init without installing anything first:

npx eve@latest init my-agent

That command creates the project, installs dependencies, initializes Git, starts the dev server, and opens an interactive terminal UI. Type a message and you’ll watch the model loop run in real time.

A couple of things worth knowing:

  • Pass --channel-web-nextjs if you want a Web Chat app generated alongside the agent
  • eve init holds the terminal, so hit Ctrl+C to get your shell back before you start editing files
  • To add Eve to a project you already have, run eve init . from a folder that has a package.json and no agent/ files yet. It adds eve, ai, and zod without touching the rest

If you’d rather wire it in by hand, install the three dependencies and declare a Node 24 engine:

npm install eve@latest ai zod
{
  "engines": {
    "node": "24.x"
  }
}

Then write the two files Eve needs. agent/instructions.md:

You are a concise assistant. Use tools when they are available.

And agent/agent.ts:

import { defineAgent } from "eve";

export default defineAgent({
  model: "anthropic/claude-sonnet-4.6",
});

Even at this size the agent can already do real work, because the default harness ships with file, shell, web, and delegation tools out of the box.

Letting a coding agent do the setup

If you’re using Claude Code, Cursor, or a similar tool, hand it this prompt: “Set up an eve agent: read the eve docs (bundled at node_modules/eve/docs once eve is installed), scaffold with npx eve@latest init <name>, add a typed tool at agent/tools/get_weather.ts, run it with npm run dev, then create a session, stream it, and send a follow-up.” Once eve is installed, the full docs live locally in node_modules/eve/docs/, so the model doesn’t have to guess at an unfamiliar API.

Step 2: Give the agent a tool

A tool is a typed action the agent can call: hit an API, run a query, write a file. The filename becomes the tool name the model sees, and it has to be snake_case. Create agent/tools/get_weather.ts:

import { defineTool } from "eve/tools";
import { z } from "zod";

// The model sees this tool as `get_weather`, from the filename.
export default defineTool({
  description: "Get the current weather for a city.",
  inputSchema: z.object({ city: z.string().min(1) }),
  async execute({ city }) {
    return { city, condition: "Sunny", temperatureF: 72 };
  },
});

The pieces of a tool:

  • A filename slug under agent/tools/, which is the model-facing name
  • A description written for the model, telling it what the tool does
  • An inputSchema, a Zod schema (pass z.object({}) for no input)
  • An execute(input, ctx) function, sync or async, that does the work

Tools run in your app runtime with full access to process.env, not inside the sandbox, so they can import shared code from lib/ and read your secrets directly. One thing to keep in mind: a step that gets interrupted mid-execution re-runs on resume, so make side effects like charges or emails idempotent, or gate them behind approval (more on that below).

Step 3: Run it and send a message

A scaffolded app has a dev script:

npm run dev

If you wired Eve in by hand and have no dev script, run the binary through npx eve dev instead. Either way you land in the terminal UI. Type “What’s the weather in Brooklyn?” and you’ll see the calls happen in order: the get_weather call, then its result, then the reply.

Every Eve app also exposes the same stable HTTP API. Start a durable session with curl:

curl -X POST http://127.0.0.1:3000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"What is the weather in Brooklyn?"}'

The response hands you back two things you’ll reuse: a continuationToken in the body to resume the conversation, and an x-eve-session-id header that identifies the run. Attach to the stream with the session id:

curl http://127.0.0.1:3000/eve/v1/session/<sessionId>/stream

The stream is NDJSON. For this run you’ll see session.started, actions.requested (the tool call), action.result, message.completed (the reply), and session.completed. To continue the conversation, post a follow-up with the token:

curl -X POST http://127.0.0.1:3000/eve/v1/session/<sessionId> \
  -H 'content-type: application/json' \
  -d '{"continuationToken":"<token>","message":"Now do Queens."}'

That’s the hello-world. A weather bot isn’t useful to anyone, so let’s make it reachable where people actually work.

Step 4: Connect the agent to Slack

A channel is the adapter between a platform and your agent. It normalizes incoming messages, owns the resume token for that surface, and decides how replies get delivered. The Slack channel answers @mentions and DMs, replies in threads, shows typing indicators, and turns human-in-the-loop prompts into Slack buttons.

The nice part: credentials run through Vercel Connect, which handles both the outbound bot token and inbound webhook verification. There’s no SLACK_BOT_TOKEN or SLACK_SIGNING_SECRET for you to babysit.

First, set up a Connect client and point its trigger at Eve’s Slack route:

npm install -g vercel@latest && export FF_CONNECT_ENABLED=1
vercel connect create slack --triggers
vercel connect detach <uid> --yes
vercel connect attach <uid> --triggers --trigger-path /eve/v1/slack --yes

The create step provisions a destination at the default Connect path, then detach/attach re-points it at /eve/v1/slack, which is where Eve actually listens. The --triggers flag turns on Slack Event Subscriptions; without it, Slack never delivers app_mention or message.im events.

Now add the channel. Either scaffold it with eve channels add slack, or write agent/channels/slack.ts by hand:

import { connectSlackCredentials } from "@vercel/connect/eve";
import { slackChannel } from "eve/channels/slack";

export default slackChannel({
  credentials: connectSlackCredentials("slack/my-agent"),
});

connectSlackCredentials returns the bot token and webhook verifier, keeping token rotation and request verification inside Connect instead of your code. Deploy once the trigger and channel file are ready:

VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1 vercel deploy --prod

That flag lets the Vercel CLI recognize Eve as a framework during the build.

Pulling in thread context

By default the channel gives you the triggering mention, but not the earlier replies in the thread. If you want the agent to read what was said before it was called in, load the prior messages and return them as context:

import { defaultSlackAuth, loadThreadContextMessages, slackChannel } from "eve/channels/slack";
import { connectSlackCredentials } from "@vercel/connect/eve";

export default slackChannel({
  credentials: connectSlackCredentials("slack/my-agent"),
  async onAppMention(ctx, message) {
    const auth = defaultSlackAuth(message, ctx);
    const prior = await loadThreadContextMessages(ctx.thread, message, {
      since: "last-agent-reply",
    });
    if (prior.length === 0) return { auth };
    const transcript = prior
      .map((m) => `${m.isMe ? "you" : (m.user ?? "user")}: ${m.markdown}`)
      .join("\n");
    return { auth, context: [`Recent thread messages since your last reply:\n\n${transcript}`] };
  },
});

Using since: "last-agent-reply" means repeated mentions in one thread only inject what’s new, so you don’t re-feed the whole history every turn.

Tell people they're talking to a bot

Eve doesn’t add an “I’m an AI” disclosure for you. Depending on where your users are, you may be legally required to disclose that they’re talking to an automated system. Bake it into your instructions.md or your channel responses.

Step 5: Connect the agent to Discord

Discord works through HTTP Interactions: slash commands, message components, and modals. Discord enforces a three-second deadline to acknowledge a command, so the channel verifies the signature, acknowledges right away, and runs the actual work in the background. You don’t have to think about any of that; it’s handled.

The minimal agent/channels/discord.ts:

import { discordChannel } from "eve/channels/discord";

export default discordChannel();

Discord needs three environment variables:

DISCORD_PUBLIC_KEY=...      # verifies the signature headers
DISCORD_APPLICATION_ID=...  # edits the deferred response, sends followups
DISCORD_BOT_TOKEN=...       # proactive messages, fallback, typing indicators

The route is POST /eve/v1/discord by default. Paste that public URL into your Discord application’s Interactions Endpoint URL field in the Developer Portal.

Registering commands is on you, not the channel. A string option named message lines up with Eve’s default prompt extraction:

curl -X PUT "https://discord.com/api/v10/applications/$DISCORD_APPLICATION_ID/commands" \
  -H "Authorization: Bot $DISCORD_BOT_TOKEN" -H "Content-Type: application/json" \
  -d '[{"name":"ask","description":"Ask the eve agent","type":1,
    "options":[{"name":"message","description":"What should the agent do?","type":3,"required":true}]}]'

Use guild commands during development; they propagate much faster than global ones. Here’s a slightly fuller channel that decides auth per command and posts the reply back:

import { discordChannel } from "eve/channels/discord";

export default discordChannel({
  onCommand: (ctx, interaction) => ({
    auth: {
      principalId: interaction.user.id,
      principalType: "user",
      authenticator: "discord",
      attributes: { channel_id: interaction.channelId, guild_id: interaction.guildId ?? "" },
    },
  }),
  events: {
    "message.completed"(eventData, channel, ctx) {
      if (eventData.finishReason === "tool-calls") return;
      if (eventData.message) channel.discord.post(eventData.message);
    },
  },
});

One limitation to note: inbound file attachments aren’t supported on the Discord channel today, while Slack does stage them. If your agent needs to read uploaded files, plan around Slack or a web channel.

The thing I appreciate is that the same agent logic serves both platforms. Your get_weather tool doesn’t know or care whether the question came from Slack, Discord, the terminal, or a browser. Write the behavior once, expose it everywhere.

Step 6: Use open source models instead of Claude

The default model is Claude Sonnet, but you’re not married to it. We learned the hard way over the past couple of years that a model you depend on can be deprecated or pulled out from under you, so being able to switch matters. Eve makes the model a single line of config.

You have two routing options.

Which open model should you reach for? Models like GLM-5.1, Kimi K2.6, and Qwen 3.6 hold up well for agentic tool-calling work at a fraction of the cost of frontier models. I went deep on the trade-offs in my guide to the best open source LLMs to replace Claude, so check that if you’re picking one for a real workload. The short version: route cheap, fast models to simple turns and save the expensive ones for the hard problems. Eve lets you do exactly that, even per subagent.

Per-task model routing

Because the model is just config, you can run subagents on different models. A research subagent might use a cheap long-context model while the root agent stays on something stronger. You build the skill once and route it wherever makes sense.

Step 7: Add tools from external services with connections

Beyond the tools you write, Eve can pull in tools from external MCP servers and OpenAPI documents. These live in agent/connections/, and the model never sees the URL or credentials, it discovers the tools and calls them by name.

A connection to Linear’s MCP server looks like this:

import { defineMcpClientConnection } from "eve/connections";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace: issues, projects, cycles, and comments.",
  auth: {
    getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }),
  },
});

For anything that touches money, deletes data, or sends messages, gate it behind approval. The helpers from eve/tools/approval give you never(), once() (ask the first time in a session), and always() (ask every time). The same pause-and-resume flow that powers human-in-the-loop tools handles it.

Step 8: Deploy it

You’ve got a working agent. Now it needs to live somewhere. Eve runs the same way locally, on Vercel, and on a plain Node host, so going to production is mostly mechanical.

Option A: Deploy on Vercel

eve build compiles the agent and writes the host output. On Vercel that’s the Build Output bundle under .vercel/output, plus the compiled artifacts under .eve/. Then deploy with the CLI or by pushing to a Git-connected project:

vercel deploy

Before that first production request, work through the short checklist:

  • Set a model credential and any route-auth secrets in the deployment environment, never in source
  • Replace the scaffolded placeholderAuth() with a real auth policy (Basic, JWT, OIDC, or a custom verifier); an unconfigured app fails closed and rejects browser traffic, which is the safe default
  • Confirm the sandbox backend matches the environment (vercel() on Vercel, defaultBackend() elsewhere)

Once deployed, the platform auto-detects Eve and surfaces an Agent Runs tab under your project’s Observability view, where you can browse sessions and read each conversation’s trace.

Option B: Self-host on your own VPS

If you’d rather not be tied to Vercel’s platform, Eve runs as a normal Node service behind your own process manager or reverse proxy:

eve build
PORT=3000 eve start --host 0.0.0.0

Outside Vercel, Eve writes the standard Nitro output under .output/, the Workflow SDK uses its local world (storing state under .workflow-data), and defaultBackend() picks a local sandbox backend. A few things to make explicit when self-hosting:

  • Put .workflow-data on persistent storage so session state survives restarts
  • Use a direct provider model with OPENAI_API_KEY / ANTHROPIC_API_KEY, or keep AI_GATEWAY_API_KEY if you still want Gateway routing
  • Replace vercelOidc() with auth your host can verify
  • If your agent uses schedules, make sure your host runs Nitro’s scheduled tasks, or trigger the same work from your own cron

The cleanest way I’ve found to run a Node service like this on a VPS is Dokploy, an open source, self-hostable alternative to Vercel and Heroku. It gives you Git-based deploys, automatic HTTPS via Traefik, environment variable management, and logs, all from a dashboard you control. My Dokploy install guide covers getting it running on a fresh server.

The deploy flow for an Eve agent maps almost exactly onto a normal Node app, so if you’ve deployed anything with Dokploy before, this will feel familiar. My walkthrough on deploying TanStack Start on a VPS with Dokploy shows the full pattern (build command, start command, environment variables, domain), and the same steps apply here: set the build command to eve build, the start command to eve start --host 0.0.0.0, expose the port, and add your model and auth secrets in the environment panel.

Why self-host the agent

Running on your own VPS means your conversation data and credentials stay on infrastructure you control, costs are predictable, and you’re not exposed to platform pricing changes. The trade-off is you handle backups, scaling, and the persistent .workflow-data storage yourself.

Verify the deployment

Whichever path you took, smoke-test the live routes. Health first, then a real turn:

curl https://<your-app>/eve/v1/health

curl -X POST https://<your-app>/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Hello from production"}'

You can also drive the live deployment with the dev terminal, which is handy for a quick production check:

eve dev https://<your-app>

What to build next

The weather bot was just a way to see the loop run. The interesting part is what you put in tools/ and skills/. A content-repurposing agent that drafts social posts and calls image generation tools. A lead-research agent that pulls from your CRM. A support triage bot living in your Slack. The scaffolding is the same every time, which is the whole point: you stop reinventing the harness and spend your time on the actual capability.

  • Add a skill in agent/skills/ for any multi-step procedure the model should load only when relevant
  • Spin up subagents for parallel or specialist work, each on its own model
  • Add schedules for recurring jobs like a daily digest
  • Put a Next.js front end in front of the agent with the useEveAgent hook

FAQ

Is Vercel Eve free and open source?

Yes, Eve is open source and lives on GitHub. You can run it entirely on your own infrastructure with your own model keys. The optional conveniences (AI Gateway, hosted Sandbox, Vercel Connect, the Agent Runs dashboard) are Vercel platform features you can opt into, not requirements. It’s in beta, so expect some churn.

Do I need a Vercel account to use Eve?

No. You need a model credential, which can be a direct provider key like ANTHROPIC_API_KEY or OPENAI_API_KEY. A Vercel account makes the AI Gateway, Slack credentials via Connect, and hosted sandboxes easier, but you can self-host the whole thing on a VPS with Dokploy and never link a Vercel project.

Can one agent serve Slack and Discord at the same time?

Yes. Add both agent/channels/slack.ts and agent/channels/discord.ts. The agent’s instructions, tools, and skills are shared; each channel just adapts the platform’s input and output. Your tools don’t need to know which surface a message came from.

Can I use open source models instead of Claude?

Yes. Change the model value in agent/agent.ts. Use a gateway string id like z-ai/glm-5.1, or install an AI SDK provider and point its baseURL at OpenRouter, Together, Groq, or a self-hosted endpoint. See my open source LLM comparison for picking one.

What does 'durable' mean for an Eve session?

A session can stream progress, call tools and subagents, pause for human approval, resume after the answer arrives, and keep state across many turns. It’s built on the open source Workflow SDK, which makes runs resumable and crash-safe. A completed step never re-runs; an interrupted step does, so make side effects idempotent.

Wrapping up

Eve takes the part of agent-building that’s usually a tangle (channels, durable state, model routing, deploys) and turns it into a folder you can read top to bottom. You scaffold with one command, add a tool as a single file, point a channel at Slack or Discord, and pick whatever model fits the job and your budget. Deploy it on Vercel for the zero-config path, or self-host it on a VPS with Dokploy when you want to own the whole stack.

If you’re coming from another framework, the switch costs you almost nothing, since your tools and skills are plain TypeScript and Markdown you can carry elsewhere. Start with the weather hello-world, then replace it with something your team would actually use.

Read the Eve docs