Skip to content

EVE: durable AI agents, assembled from files

Jaskaran Singh19 min read

EVE is Vercel's open-source framework for building durable backend AI agents — filesystem-first, Apache-2.0, public on GitHub since June 2026 and already moving fast: v0.66.3 as of writing, 5.4k stars, 1,580 commits. The pitch on the tin: "Like Next.js for agents. Build durable agents with one folder."

  • What it is — a filesystem-first framework for durable backend AI agents
  • Who built it — Vercel, open source under Apache-2.0
  • Launch status — public beta; repo public since June 2026, v0.66.3 as of writing
  • Where it runs — Vercel, or any Node 24+ host as a plain Node service
  • Start with — npx eve@latest init my-agent
The eve.dev homepage: "Build durable agents with eve — Like Next.js for agents. Build durable agents with one folder."

Why "durable" is the word that matters: a real agent is not a stateless HTTP request. It is 2:14 am, a customer asks your support agent in Slack to refund a double charge, the agent drafts the refund and waits for a human sign-off, your on-call engineer approves from their phone — and while that thread was open you shipped two deploys, and the customer never noticed, because the conversation did not live in a server's RAM. That is the bar a production agent has to clear, and almost every framework I have used treats it as your problem: glue together a queue, a database, and a prayer. EVE makes it the default.

I spent a day in the docs end to end. This is the mental model, the real business workflows it is built for, and the parts that decide whether it fits your product.

What businesses actually run on it

Before the architecture, the receipts. These are the workflows EVE's own templates and patterns are shaped around — each one is a business process that used to need a person babysitting a queue:

The eve.dev templates gallery: Chat, LLM council, Design, Slack, GitHub maintainer, Software factory, Incident response, Marketing team, Sanity copilot, Social media, Mux video — each a starting repo, not a demo.

Support and billing ops. An agent that checks a charge, drafts the refund, and parks until a human approves. The approval is not a checkbox in a dashboard — it is the durable execution model: the turn suspends with zero compute, resumes when the engineer taps approve, and every step lands on an audit trail. Compliance gets the paper trail for free.

Incident response. Vercel's sre template: a Datadog or webhook alert opens a session, the agent investigates with read-only Datadog, GitHub and Vercel tools, posts its findings to Slack, and stops at the line where judgment is required. A rollback — a write action with real blast radius — waits for a human. On-call toil shrinks from "wake up and triage" to "read a summary and decide."

The Monday-morning maintainer. The kody template runs on a cron schedule: every week it sweeps open issues and pull requests, emails you a digest, answers @mentions, and works delegated Linear issues. The repo owner stops triaging; the agent does the sweep, the human keeps the veto.

A marketing team in files. The marketing-team template: a lead agent routes work to specialists for positioning, long-form content, SEO, social and email, then publishes through Notion, Typefully and Resend. Each specialist is a subagent with its own instructions and sandbox; nothing crosses the boundary implicitly.

A software factory. The foreman template takes tasks from GitHub and Linear, runs each through classifier, analyst, implementer and reviewer stations, and delivers a reviewed draft pull request. Humans review PRs; the factory does the queue-watching.

The common shape: agents that live where work already happens (Slack, GitHub, email), act only as far as policy allows, and wait — durably — whenever a person must decide. Now the mechanics that make that shape possible.

First run: three files and a terminal

The scaffold is one command (Node 24+):

bash
npx eve@latest init my-agent
cd my-agent
npm run dev   # reopens the terminal UI any time

You get an agent/ directory and an interactive TUI that connects a model — a ChatGPT subscription, a Vercel AI Gateway route, or an OpenAI/Anthropic key. The whole agent is two files you edit while it runs:

ts
// agent/agent.ts — model and runtime behaviour
import { defineAgent } from "eve";

export default defineAgent({
  model: "anthropic/claude-opus-5.5",
});
md
<!-- agent/instructions.md — the always-on system prompt -->
You are the support agent for Acme. Be terse. Never promise a refund
without running the refund tool and getting human approval.

No server code, no route handlers, no database migration. The TUI is just the first channel; everything below works without it.

The vercel/eve repository on GitHub — "The Open Framework for Building Agents", 5.4k stars, 588 forks, 1,580 commits, Apache-2.0.

The durability model is the product

This is the part worth internalizing, because everything else in EVE is a consequence of it — and because it is the part with a P&L shadow: a customer's Slack thread, an open incident, a refund waiting on sign-off are all sessions, and losing one mid-deploy is the difference between "the agent handled it" and a churned account. Work nests three levels deep:

  • session — the whole durable conversation. It can span days or weeks and survives process restarts and redeploys with no work on your part.
  • turn — one inbound message plus everything it triggers until the agent answers.
  • step — a durable checkpoint inside a turn: by default one model call and the tool calls that follow it.

Underneath, every session runs as a workflow on the open-source Workflow SDK — locally it persists to .eve/.workflow-data, on Vercel it becomes Vercel Workflow. The consequences are concrete:

Animated diagram: an eve session holds turns and survives redeploys; inside a refund turn each step writes a checkpoint, the process is killed mid-step, eve replays the completed steps from their recorded results, re-runs only the interrupted one, and the refund tool then parks for approval without holding compute.

Crashes are a resume, not a retry. Kill the process mid-turn, hit a timeout, redeploy — the run picks up from the last completed step. Completed steps never re-run; eve replays their recorded result. The interrupted step re-runs, which means one rule you must actually respect: make non-idempotent side effects (charges, emails) idempotent, or gate them behind approval. This is the same discipline distributed systems have always demanded — eve just makes the checkpointing automatic.

Waiting is free. When a tool needs a human sign-off or an OAuth dance, the turn parks: the workflow suspends and holds zero compute until the input arrives, whether that is in four seconds or four days. There is no polling loop to babysit.

Redeploys hand off cleanly. When a new production deployment goes out, an idle session moves to it on its next delivery — same session ID, same history, new instructions and tools. A session with live work stays on the old deployment until that work settles.

Users can steer mid-turn. A second message that lands before the agent starts answering interrupts the pending generation and applies the correction inside the same turn. After output starts, it applies at the next checkpoint. The default policy is called steer; set turnPolicy: "queue" when each turn must finish first.

The filesystem is the framework

There is no registration step anywhere in eve. The compiler walks agent/ and every file it finds becomes a capability, named by its path:

Animated diagram: the eve compiler walks an agent directory and each file becomes a capability named by its path — a tool, a channel, a skill, a schedule, a memory slot and a subagent light up in turn.
FileBecomes
agent/tools/*.tsTyped tools, named by filename
agent/channels/*.tsFront doors: Slack, Discord, HTTP, custom webhooks
agent/skills/*.mdProcedures the model loads on demand
agent/schedules/*.tsCron jobs
agent/memory/*.tsMemory slots
agent/subagents/<name>/Specialist agents with their own sandbox and state
agent/sandbox/workspace/Files seeded into the sandbox's /workspace

Skills deserve a mention because the mechanic is clever: a skill is markdown following the SKILL.md convention, and eve advertises only each skill's description to the model. The full body enters context only when a turn calls for it, via a framework-owned load_skill tool. Progressive disclosure, so a hundred procedures do not cost a hundred turns of context.

The honest tradeoff of filename-as-API: renames are breaking changes, and discovery is magic until you internalize the agent files reference. I would take that trade — it is grep-able, diff-able, and reviewable in a PR, which no decorator registry is.

Tools: typed actions with a human gate

A tool is a file. The filename is the name the model sees:

ts
// agent/tools/refund_charge.ts — the filename IS the tool name
import { defineTool } from "eve/tools";
import { always } from "eve/tools/approval";
import { z } from "zod";

export default defineTool({
  description: "Refund a charge.",
  inputSchema: z.object({ chargeId: z.string(), amount: z.number() }),
  approval: always(), // every call parks until a human signs off
  async execute(input, ctx) {
    return refund(input); // runs in the app runtime, with process.env
  },
});

inputSchema is Zod (or any Standard Schema, or plain JSON Schema), validated before execute runs. The ctx handed to execute carries the session metadata, an abort signal, and ctx.getSandbox() for when a tool needs isolated filesystem or shell access.

That approval: always() line is the whole compliance story in one word: the difference between an AI that can move money and an AI that can only propose moving money. Finance signs off on the second; the audit trail comes from the durable step records.

Three details separate this from a typical SDK:

  • label.start projects a human-readable line ("Deploy storefront to production") into channel activity, so users see what is happening without the raw JSON.
  • toModelOutput shrinks what the model sees while channels still receive the full output — the model gets the gist, your Slack card gets the rich payload.
  • defineWorkflowTool turns a tool into a durable workflow of its own, for waits that should outlive the initiating step; with execution: "background" the model gets a task receipt immediately and the result arrives later as its own turn.

Channels: one runtime, many front doors

A channel is the adapter between a platform and your agent. It normalizes platform input into a user message, owns the address that maps a platform conversation to its durable session, and decides how responses get back. The agent runtime itself has zero channel-specific logic.

eve ships first-class channels for Slack, Discord, Microsoft Teams, Telegram, Twilio, GitHub, Linear, and iMessage/SMS (via Linq and Photon), plus a base HTTP channel at /eve/v1 — that is what the TUI, curl, and the React useEveAgent client all talk to. Anything else is a defineChannel file with ordinary route handlers.

For a business this is the distribution argument: your customers and staff are already in Slack, Teams and email. The channel file is the entire cost of putting the agent where the work happens — no new app to launch, no tab tax, no adoption program.

The delivery semantics are the interesting part. Channels default to the steering behaviour described above; background task results default to "auto" (report each result as it becomes useful) while schedule-started sessions default to "cohort" (wait for overlapping tasks, deliver together). These are one-line settings, but they are the difference between an agent that spams a channel and one that speaks when it has something to say.

Schedules: cron that can wait

A schedule is a file under agent/schedules/ with a cron expression and exactly one of two bodies:

ts
// agent/schedules/digest.ts
import { defineSchedule } from "eve/schedules";
import slack from "../channels/slack";

export default defineSchedule({
  cron: "0 9 * * *",
  async run({ to, waitUntil, appAuth }) {
    waitUntil(
      to(slack, { channelId: "C0123ABC" }).send(
        "Prepare the daily reports. Send nothing if there is nothing worth sending.",
        { auth: appAuth },
      ),
    );
  },
});

The markdown form is fire-and-forget: eve runs the agent on the prompt and discards the output. The run form is a handler that selects a channel target with to(...), keeps the cron task alive with waitUntil(...), and runs as the app principal with appAuth. Handler sessions run on the same durable runtime as everything else — so a scheduled agent that has to wait for a Slack reply parks, it does not drop the thread.

This is the machinery behind the Monday digest and the 9 am report from the examples above: a cron file replaces the intern you hired to pull numbers every morning, and the "send nothing if there is nothing worth sending" instruction is what keeps it from becoming notification noise.

On Vercel, every schedule compiles into a Vercel Cron Job automatically. One gotcha to write on your hand: eve dev never fires schedules on their cron cadence — you trigger them out of band with a dev-only dispatch route (POST /eve/v1/dev/schedules/<name>), and only eve start or a Vercel deployment runs the real clock.

Memory: slots with a fixed boundary

Memory is declared as slots, and the split of responsibilities is fixed: eve owns the slot name, the scope resolution from trusted auth context, and when recall and capture run; the provider owns storage, ranking, and what to extract.

ts
// agent/memory/profile.ts
import { defineMemory } from "eve/memory";
import { byPrincipal } from "eve/memory/scope";
import { fileMemory } from "eve/memory/file";

export default defineMemory({
  description: "Stable facts and preferences about the caller.",
  provider: fileMemory(), // built-in; swap for a hosted provider
  scope: byPrincipal,     // one memory per authenticated caller
});

Before each turn eve asks the provider to recall relevant context; after each turn it offers capture; provider tools reach the model namespaced as <slot>__<tool>. The built-in fileMemory() keeps one bounded document per scope and is the shortest path to working memory. Hosted providers (Supermemory, Upstash AgentKit, Kybernesis Arcana) drop in behind the same definition, and the custom provider contract is three handlers — anything that can read and write under a scope key qualifies, Postgres included.

The detail I appreciate most: scope resolves from trusted authentication, never model input, and returning null disables the slot for that request. There is no silent fallback to shared memory — tenant isolation fails closed.

In business terms: the agent remembers that this customer is on the annual plan and hates being called — per customer, not per chat window — and one tenant's context can never leak into another's because the scope comes from auth, not from whatever the model was told.

Two rooms and a wall

The security model is one diagram, and it is the slide I would show in a design review:

Animated diagram: the model calls tools that run in the trusted app runtime where secrets live, while the sandbox stays isolated with no secrets; a key tries to cross the trust boundary and bounces back, bash commands are proxied through it as tool calls, and credential brokering adds auth headers at the sandbox firewall.
App runtimeSandbox
process.env / secretsyesno
Your Node.js codeyesno
Networkunrestrictedpolicy-controlled
Filesystemthe app's ownisolated /workspace

Tool executors, model calls, connections, and the durable workflow all run in the app runtime, with full Node access. The model's shell commands run in the sandbox — a per-session environment with its own filesystem, no environment variables, and no path back into the runtime. Even the built-in bash, read_file, and write_file tools live in the runtime and proxy into the sandbox, so sandbox work passes the same approval and instrumentation path as any other tool.

When a sandbox process genuinely needs authenticated network access — a private git clone, an authenticated curl — credential brokering injects the auth header at the sandbox's network firewall. The process sees the response; the secret never leaves the runtime.

Two defaults worth knowing before you ship: routes fail closed (unauthenticated requests get a 401 until you explicitly allow them, and the scaffold's placeholderAuth() keeps a half-configured app closed in production), and platform channels verify HMAC signatures in constant time, never trusting identity fields from the request body.

One refund, end to end

Each section above covers one piece of eve. Put together, they make the 2:14 am refund from the opening work. Here is that refund again, this time naming the part of eve that handles each moment:

Animated diagram: one refund end to end — a Slack message opens a session, the agent looks up the charge and drafts the refund, refund_charge parks behind approval: always() with zero compute while two deploys ship, the on-call engineer approves 17 minutes later, the refund runs in the app runtime, and every step lands in the audit trail.
  1. The message arrives. The customer writes in a Slack thread. The Slack channel file verifies the request signature, turns the message into a user message, and maps the thread to its durable session. The first message opens a new session; later replies in the same thread join it.
  2. Memory is recalled. Before the turn starts, the profile slot recalls what eve knows about this caller, scoped from the verified Slack identity. For example, the customer is on the annual plan. No other customer's context can load here.
  3. The agent works in steps. Step one calls lookup_charge and finds the duplicate. Step two drafts a $42.00 refund. Each step checkpoints, so neither will run again, whatever happens next.
  4. The refund parks. Step three calls refund_charge, which is declared with approval: always(). The turn suspends. No process is waiting, no polling loop runs, and no compute is held. The label.start line in the tool shows the approver a readable summary instead of raw JSON.
  5. Life goes on. You ship two deploys while the approval waits. Neither affects the refund, because the turn lives in the workflow store, not in a server's memory. If a pod crashes during those 17 minutes, nothing is lost either, because there is nothing in RAM to lose.
  6. A human decides. The on-call engineer taps approve on their phone. The workflow wakes up and continues step three from where it parked.
  7. The money moves on the trusted side. execute runs in the app runtime, where the payment key lives in process.env. The model never sees the key. It sees only the tool's result, trimmed by toModelOutput if the payload is large.
  8. The agent replies, and the record stays. A final model call writes the reply in the thread. After the turn, the memory slot is offered anything worth keeping. Every step, including who approved and when, stays in the durable step records.

None of these eight steps needed a queue, a cron-driven poller, or a state table that you designed. That is the real argument for eve: the pieces were built to fit together, so the glue code you would normally write is already part of the framework.

Shipping it

On Vercel, eve deploy gives you Vercel Workflow for durability, Vercel Sandbox microVMs for the isolated side, and every schedule wired into Vercel Cron. A Next.js frontend integrates via eve/next in next.config.ts, with useEveAgent for the browser client — the chat template on eve.dev/templates is exactly that stack with auth and persistence pre-wired.

Self-hosting is deliberately boring: eve build && eve start serves a Nitro Node build, schedules run in-process, and the workflow state store is swappable (Postgres world included). Sandbox backends are pluggable too — Docker, microsandbox VMs, or a pure-JS shell when you do not need real isolation.

Before you ship: a checklist

Everything on this list is covered earlier in the post. Here it is in one place, in the order I would check it before sending real customer traffic to an eve agent:

  • Pin the eve version. This is a beta with breaking changes between minor releases. Read the changelog before every upgrade, and upgrade on purpose, not through a loose semver range.
  • Audit every tool that writes. Anything that charges, emails, deletes, or deploys should either be safe to run twice or sit behind approval. The interrupted step re-runs after a crash, so "it probably will not crash" is not a plan.
  • Replace placeholderAuth(). It keeps a half-configured app closed. That is a safe default, but it is not an auth strategy. Wire real authentication before launch, and confirm that unauthenticated requests still get a 401.
  • Check memory scopes. Every slot should resolve its scope from trusted auth, and return null when there is no caller. Test with two accounts and confirm that neither can recall the other's context.
  • Pick a turn policy on purpose. steer suits chat, where users correct themselves mid-thought. queue suits workflows where each turn must finish before the next one starts.
  • Fire every schedule once. eve dev never runs cron on its own clock. Trigger each schedule through the dev dispatch route before you trust it in production.
  • Trim model-facing output. Big tool payloads cost context on every turn. Use toModelOutput to give the model the gist, and let channels keep the full result.
  • Self-hosting? Choose the stores deliberately. Pick the workflow state store (Postgres is supported) and a sandbox backend that actually isolates, such as Docker or microsandbox VMs. The pure-JS shell is for when you do not need isolation.

Where it fits — and where it doesn't

Fits, with a measurable payoff. Support teams: deflect the tier-1 queue (status checks, plan questions, refund drafting) while a human keeps the veto on anything that moves money. On-call: cut triage from "wake up and investigate" to "read a summary and decide." Ops and finance: replace manual sweeps — stale invoices, mismatched records, open PRs — with a cron file and an approval gate. The templates gallery is basically this list, and each one is a starting repo, not a demo.

Doesn't fit — yet. If your "agent" is a stateless one-shot tool call inside a request, the AI SDK alone is smaller. If you need frozen APIs, read the changelog first: this is a beta with real churn (v0.65 alone removed the todo tool and rebuilt ask_question). Pin your version and upgrade deliberately.

The bet, stated plainly: the durability model is the product, and the filesystem is the developer experience. Both are the boring choices — checkpoints and files — which is exactly why I trust them more than another orchestrator with a novel graph abstraction. The hard problems in agents are waiting, resuming, and not leaking secrets; eve makes those the default path instead of your weekend.

I build agents and MCP servers for teams that want this capability without the security review becoming its own project — book a call if that is your year.

More posts.