Skip to content
Docs

How to add tools to your eve agent

Add tools to an eve agent by creating a TypeScript file under agent/tools/ with defineTool, and gate sensitive ones on human approval with needsApproval.

6 min read
Last updated June 19, 2026

Tools give an eve agent typed actions it can call, such as hitting an API, running a query, or writing a file. With them, the agent does the real work rather than just describing it. eve exposes any tool you place under agent/tools/, and the filename becomes the tool name the model sees. Unlike skills, there's no separate CLI to install tools: you author each one as a TypeScript file with defineTool, and eve discovers it with no registration step.

This guide walks you through writing a basic tool, gating a sensitive one on human approval, and then confirming that your agent calls the tool for the right requests.

Before you begin, you need an eve project and Node.js installed.

  • To create a new project, run npx eve@latest init my-agent.
  • To add eve to an existing app, follow the quickstart steps.

eve discovers the files under agent/tools/ and advertises each tool's name, description, and input schema to the model. During this discovery step the model sees only those descriptors, never your implementation. When the model decides a tool fits the task, it calls the tool with arguments that match your inputSchema, and eve runs your execute function in your app runtime with full access to process.env and shared code in lib/, not in the sandbox.

eve never runs a tool during discovery. Only what the model actually calls gets executed, and completed steps don't re-run, so your tools stay predictable across a session. There's no separate registry to keep in sync: add the file and eve discovers it, move or rename it and its identity moves with it.

The smallest tool is a single TypeScript file under agent/tools/. Its name comes from the filename, which must be snake_case ASCII, so a file at agent/tools/get_weather.ts is exposed to the model as get_weather.

Create agent/tools/get_weather.ts:

agent/tools/get_weather.ts
import { defineTool } from "eve/tools";
import { z } from "zod";
export default defineTool({
description: "Get the current weather for a city.",
inputSchema: z.object({ city: z.string().min(1) }),
async execute({ city }, ctx) {
return { city, condition: "Sunny", temperatureF: 72 };
},
});

Every tool definition needs four things:

  • Filename slug under agent/tools/: the model-facing name.
  • description: what the tool does, written for the model to read.
  • inputSchema: a Zod schema (or any Standard Schema, or a plain JSON Schema object). This is required. For a tool that takes no input, pass z.object({}). Zod and Standard Schema infer the input type in execute.
  • execute(input, ctx): the implementation, which can be sync or async. The ctx argument carries runtime accessors like ctx.session, ctx.getSandbox(), and ctx.getSkill(id).

When a tool returns structured data, add an optional outputSchema, which also types the execute return when you use Zod or Standard Schema. If the full return is richer than the model needs, project it down with toModelOutput, which receives the typed execute return and shapes only what the model sees. Channel handlers and hooks still receive the full output, so a channel can render rich platform output that the model never reads:

agent/tools/get_weather.ts
toModelOutput(output) {
return { type: "text", value: `Report for ${output.domain}: score ${output.score}.` };
},

Don't return secrets, credentials, unnecessary personal data, or unbounded sensitive content from a tool. Filter, minimize, and redact tool outputs before returning them, because the model and any connected channel can see what you return.

Some actions shouldn't run without a person signing off, such as issuing a refund, sending an email, or anything irreversible. A tool can require approval before it runs: set needsApproval with the helpers from eve/tools/approval.

Create agent/tools/refund_charge.ts:

agent/tools/refund_charge.ts
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() }),
needsApproval: always(), // or once() / never() / a predicate
async execute(input) {
return refund(input);
},
});

The helpers cover the common cases:

HelperBehavior
never()Never require approval. This is the default when you omit needsApproval.
once()Require approval the first time the tool runs in a session, then auto-allow.
always()Require approval before every call.

When the decision depends on the input, pass your own predicate instead of a helper. It receives { toolName, toolInput, approvedTools } and returns a boolean. Guard the access, since toolInput can be undefined. To require approval only when an amount crosses a threshold:

agent/tools/refund_charge.ts
needsApproval: ({ toolInput }) => (toolInput?.amount ?? 0) > 1000,

Because an omitted needsApproval behaves like never(), tool calls can run without human approval by default. Require approval or another safeguard for sensitive, irreversible, regulated, financial, healthcare, employment, housing, legal, safety-impacting, user-impacting, or external side-effecting actions. Gating a side effect this way also keeps non-idempotent work safe: a charge or email behind always() can't fire from a re-run step without a fresh human decision.

When a gated tool is called, the run parks durably at session.waiting until a person answers, then picks back up exactly where it left off. The pause survives restarts, since nothing is held in memory while it waits.

Here's how a waiting run resolves:

  • eve emits an input.requested stream event carrying the pending approval.
  • The client answers with structured inputResponses keyed by request ID, or with a normal follow-up message.
  • Follow-up text that matches an option label automatically resolves the request.

Approval is one of two ways a tool can pause for a person. The other is the built-in ask_question tool, which lets the agent ask the user a clarifying question using the same protocol. It's part of the default harness, so you don't define it yourself. The model calls it with:

  • prompt: the question to put to the user.
  • options (optional): choices that channels render as buttons or a select menu.
  • allowFreeform: whether the user can answer with free text.

The rule of thumb: reach for ask_question when the model is missing information it shouldn't guess at, and needsApproval when the model has decided what to do, but a person should sign off first.

Start your agent and send a request that needs the tool. The init command starts the dev server for you when you scaffold a project; otherwise, use the dev script from your project's README. When a request matches, the model calls the tool with arguments from your inputSchema, and eve runs your execute function and returns the result to the model.

To see which tools ran, along with timing and token usage, open Agent Runs in the Vercel dashboard.

  • Tools in eve: the complete defineTool API, ctx, outputSchema, and toModelOutput.
  • Human-in-the-loop: gate a tool on approval, or have the agent ask the user a question.
  • Default harness: the built-in file, shell, web, and delegation tools, and how to override or disable them.
  • Skills in eve: on-demand procedures the model loads when a task calls for them, for guidance rather than actions.
  • Dynamic capabilities: resolve a tool set per session with defineDynamic.

Was this helpful?

supported.