---
title: Tools
description: Define typed actions the agent can call, and gate sensitive ones on human approval.
---

# Tools



A tool is a typed action the agent can call, such as hitting an API, running a query, or writing a file. The action stays in code you control. Tools run in your app runtime with full access to `process.env`, not in the [sandbox](/docs/sandbox).

## Define a tool

The filename is the tool name the model sees. A file at `agent/tools/get_weather.ts` is exposed as `get_weather`.

```ts title="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 };
  },
});
```

A tool definition needs:

* a filename slug under `agent/tools/`, the model-facing name.
* a `description`: what the tool does, written for the model.
* an `inputSchema`: a Zod schema (or any Standard Schema, or a plain JSON Schema object). Required. For no input, pass `z.object({})`. Zod and Standard Schema infer the `input` type in `execute`. Plain JSON Schema types it as `Record<string, unknown>`.
* an `execute(input, ctx)`: the implementation. May be sync or async.

When a tool returns structured data, add an optional `outputSchema`. With Zod or Standard Schema it also types the `execute` return.

### The `ctx` parameter

`execute` gets a `ctx` carrying the runtime accessors:

* `ctx.session`: session metadata, turn, auth, parent lineage.
* `ctx.callId`: the id of the current tool call, carried by the call's [stream events](/docs/concepts/sessions-runs-and-streaming) and approval context.
* `ctx.toolName`: the final runtime name the model called, including any namespace qualification.
* `ctx.abortSignal`: aborts when the active turn is cancelled. Pass it to cancellation-aware work; sandbox sessions from `ctx.getSandbox()` are already bound to it.
* `ctx.getSandbox()`: the live [sandbox](/docs/sandbox) handle.
* `ctx.getSkill(id)`: read a packaged [skill](/docs/skills)'s metadata and files.

Running in the app runtime is what lets a tool import shared code from `lib/`, read `process.env`, and take part in eve’s durable pause/resume model.

eve never runs authored tools during discovery. The model sees descriptors first, and only what it actually calls gets executed. Completed steps never re-run; eve replays the recorded result. A step interrupted mid-execution re-runs, so make non-idempotent side effects like charges or emails idempotent, or gate them with approval.

## Gate a tool on human approval

A tool can require a person to sign off before it runs. Set `approval` with the helpers from `eve/tools/approval`:

```ts title="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() }),
  approval: always(), // or once() / never() / a policy
  async execute(input) {
    return refund(input);
  },
});
```

Approval is one half of eve's [human-in-the-loop](./human-in-the-loop) model — the page covers the `always/once/never` helpers, input-dependent policies, and how a gated call pauses and resumes durably.

## Shape what the model sees with `toModelOutput`

By default the model sees the full `execute` return. When a tool returns rich data a channel needs for rendering but the model only needs the gist, project it down with `toModelOutput`:

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

`toModelOutput` receives the full, typed `execute` return and only affects the model. Channel event handlers and hooks still get the full output on `action.result`, so a channel can render rich platform output (Slack Block Kit, say) the model never sees. Return `{ type: "text", value }` for a summary, or `{ type: "json", value }` for a smaller object.

Tool outputs must be JSON-serializable. Return plain objects, arrays, strings, numbers, booleans, or `null`; convert values like `Date`, `Map`, `Set`, `NaN`, and cyclic objects before returning them from `execute` or from a `{ type: "json" }` `toModelOutput`.

### Send images to the model with content parts

A tool that produces an image — a screenshot, a rendered chart — can hand the pixels to a vision-capable model by returning a `content` output from `toModelOutput`. Build outputs with the `toolOutput` helpers and parts with the `toolOutputPart` helpers, both from `eve/tools`:

```ts
import { defineTool, toolOutput, toolOutputPart } from "eve/tools";

export default defineTool({
  description: "Capture a screenshot of the current page",
  inputSchema: z.object({ url: z.string() }),
  async execute(input) {
    const png = await captureScreenshot(input.url);
    return { path: png.path, screenshotBase64: png.base64 };
  },
  toModelOutput(output) {
    return toolOutput.content([
      toolOutputPart.text(`Screenshot of ${output.path}:`),
      toolOutputPart.file(output.screenshotBase64, { mediaType: "image/png" }),
    ]);
  },
});
```

The `toolOutput.text` and `toolOutput.json` builders construct the other two output shapes; hand-written literals remain valid everywhere.

File payloads must be base64 strings — raw bytes (`Uint8Array`, `Buffer`) are rejected because they do not survive eve's durable JSON boundary. Keep payloads small: a content-part image is persisted in session history and re-sent on every subsequent model call, and eve warns above 3 MiB. Sending image parts to a model without vision support fails with that provider's error, the same as image parts in user messages.

When older turns are compacted, file payloads are dropped from the summary and replaced with a text stub naming the file and media type — the model cannot re-see a compacted image. Content parts are for "look at this now"; if the agent may need an artifact again later, write it to the [sandbox](/docs/sandbox) and return its path.

Do not return secrets, credentials, unnecessary personal data, or unbounded sensitive content from tools. Filter, minimize, and redact tool outputs before returning them.

## What to read next

* [Human-in-the-loop](./human-in-the-loop): gate a tool on approval, or have the agent ask a question
* [Skills](/docs/skills): on-demand procedures the model loads when relevant
* [Default harness](/docs/concepts/default-harness): the built-in tools and how to override or disable them
* [Dynamic capabilities](/docs/guides/dynamic-capabilities): tools whose set is resolved per session with `defineDynamic`
* [Auth & route protection](/docs/guides/auth-and-route-protection): authenticate a tool to an external service


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)