Agent-readable docs

Eve Documentation

Reference for authoring and operating filesystem-first durable backend agents with the eve CLI, define* APIs, HTTP session protocol, channels, sandbox, and deployment workflows.

Pages

  1. OverviewFilesystem-first agent model, channel/harness/runtime split, public entry points (eve CLI, /eve/v1 HTTP routes, define* authoring), and the shortest path from init to a running session.
  2. InstallationNode 24+ prerequisites, npm/pnpm install paths, eve package binary, model credential options (gateway OIDC or direct provider keys), and environment file loading from the app root.
  3. QuickstartScaffold with eve init, verify discovery with eve info, iterate with eve dev, and create/stream/continue an HTTP session against /eve/v1/session.
  4. Project layoutAuthored slots under agent/, path-derived naming (no name fields), evals/ placement, subagent inheritance rules, and what compiles into .eve/ artifacts.
  5. Execution model and durabilitySession/turn/step nesting, durable workflow checkpoints, crash resume semantics, parked work (HITL, OAuth, subagents), and message delivery constraints on continuationToken.
  6. Sessions and streamingcontinuationToken vs sessionId contracts, POST /eve/v1/session and follow-up routes, NDJSON stream events, reconnect behavior, and subagent child-session attachment.
  7. Default harnessBuilt-in agent loop, shipped tools (bash, file ops, web_fetch, todo, ask_question, agent, load_skill, connection_search), compaction defaults, and override/disable patterns.
  8. Context controlAlways-on instructions vs on-demand skills, workspace visibility through sandbox tools, dynamic capabilities (defineDynamic), and subagent context isolation.
  9. Security modelApp runtime vs sandbox trust boundaries, secret brokering, connection token handling, channel signature verification, and fail-closed route auth defaults.
  10. Instructions and skillsAuthor instructions.md or instructions.ts with defineInstructions, flat and packaged skills under agent/skills/, load_skill activation, and workspace seeding to /workspace/skills.
  11. ToolsdefineTool schema and execute(ctx), HITL approval predicates (always/once/never), tool auth and requireAuth, built-in overrides, and dynamic tool resolution.
  12. ConnectionsMCP and OpenAPI connections with defineMcpClientConnection and defineOpenAPIConnection, OAuth callback routes, connection_search qualified tool names, and getToken/requireAuth flows.

Complete Markdown

# Eve Documentation

> Reference for authoring and operating filesystem-first durable backend agents with the eve CLI, define* APIs, HTTP session protocol, channels, sandbox, and deployment workflows.

## Context Links

- [Agent index](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/llms.txt)
- [Human interactive docs](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f)
- [GitHub repository](https://github.com/vercel/eve)

## Repository Metadata

- Repository: vercel/eve

- Generated: 2026-06-16T19:31:20.815Z
- Updated: 2026-06-16T19:32:25.135Z
- Runtime: Grok CLI
- Format: Documentation
- Pages: 24

## Page Index

- 01. [Overview](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/01-overview.md) - Filesystem-first agent model, channel/harness/runtime split, public entry points (eve CLI, /eve/v1 HTTP routes, define* authoring), and the shortest path from init to a running session.
- 02. [Installation](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/02-installation.md) - Node 24+ prerequisites, npm/pnpm install paths, eve package binary, model credential options (gateway OIDC or direct provider keys), and environment file loading from the app root.
- 03. [Quickstart](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/03-quickstart.md) - Scaffold with eve init, verify discovery with eve info, iterate with eve dev, and create/stream/continue an HTTP session against /eve/v1/session.
- 04. [Project layout](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/04-project-layout.md) - Authored slots under agent/, path-derived naming (no name fields), evals/ placement, subagent inheritance rules, and what compiles into .eve/ artifacts.
- 05. [Execution model and durability](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/05-execution-model-and-durability.md) - Session/turn/step nesting, durable workflow checkpoints, crash resume semantics, parked work (HITL, OAuth, subagents), and message delivery constraints on continuationToken.
- 06. [Sessions and streaming](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/06-sessions-and-streaming.md) - continuationToken vs sessionId contracts, POST /eve/v1/session and follow-up routes, NDJSON stream events, reconnect behavior, and subagent child-session attachment.
- 07. [Default harness](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/07-default-harness.md) - Built-in agent loop, shipped tools (bash, file ops, web_fetch, todo, ask_question, agent, load_skill, connection_search), compaction defaults, and override/disable patterns.
- 08. [Context control](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/08-context-control.md) - Always-on instructions vs on-demand skills, workspace visibility through sandbox tools, dynamic capabilities (defineDynamic), and subagent context isolation.
- 09. [Security model](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/09-security-model.md) - App runtime vs sandbox trust boundaries, secret brokering, connection token handling, channel signature verification, and fail-closed route auth defaults.
- 10. [Instructions and skills](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/10-instructions-and-skills.md) - Author instructions.md or instructions.ts with defineInstructions, flat and packaged skills under agent/skills/, load_skill activation, and workspace seeding to /workspace/skills.
- 11. [Tools](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/11-tools.md) - defineTool schema and execute(ctx), HITL approval predicates (always/once/never), tool auth and requireAuth, built-in overrides, and dynamic tool resolution.
- 12. [Connections](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/12-connections.md) - MCP and OpenAPI connections with defineMcpClientConnection and defineOpenAPIConnection, OAuth callback routes, connection_search qualified tool names, and getToken/requireAuth flows.
- 13. [Channels](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/13-channels.md) - HTTP and messaging ingress with defineChannel and platform factories (eve, slack, discord, teams, telegram, twilio, github), route verbs, webhook verification, and eve channels add/list scaffolding.
- 14. [Sandbox](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/14-sandbox.md) - defineSandbox backends (local vs vercel), workspace seeding from agent/sandbox/workspace, bootstrap/onSession lifecycle, build-time prewarm, and proxy execution for shell/file tools.
- 15. [Subagents and schedules](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/15-subagents-and-schedules.md) - Local subagents under agent/subagents/, remote agents with defineRemoteAgent, cron schedules (defineSchedule .ts and .md), dev schedule dispatch route, and nested delegation boundaries.
- 16. [Configure agent.ts](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/16-configure-agent.ts.md) - defineAgent fields: model (gateway id or LanguageModel), compaction thresholdPercent, modelOptions, experimental.codeMode, outputSchema, and build.externalDependencies packaging.
- 17. [Auth and deployment](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/17-auth-and-deployment.md) - Route auth walk on eveChannel, env vars and secrets, eve link/deploy flows, Vercel build output (.vercel/output), sandbox backend selection, prewarm constraints, and production verification.
- 18. [State, hooks, and session context](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/18-state-hooks-and-session-context.md) - defineState get/update persistence, defineHook stream subscribers, ctx.session/getSandbox/getSkill/getToken/requireAuth, and where managed-context APIs are valid.
- 19. [Instrumentation and evals](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/19-instrumentation-and-evals.md) - defineInstrumentation OTel setup, workflow run tags, defineEval/defineEvalConfig authoring under evals/, eve eval flags, judges, assertions, and reporters (Braintrust, JUnit).
- 20. [Client integration](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/20-client-integration.md) - eve/client Client and ClientSession, auth policies, streaming reducers, useEveAgent React/Vue/Svelte hooks, and framework plugins (eve/next, eve/nuxt, eve/sveltekit).
- 21. [CLI reference](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/21-cli-reference.md) - All eve commands (init, info, build, start, dev, link, deploy, eval, channels), flags, exit codes, .eve/ artifact paths, and the recommended edit-info-dev-build-start loop.
- 22. [TypeScript API reference](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/22-typescript-api-reference.md) - Exported define* helpers and import paths from packages/eve/src/public, ctx members, approval predicates, built-in tool defaults, and eval/client entrypoints.
- 23. [HTTP API reference](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/23-http-api-reference.md) - Stable /eve/v1 routes (health, info, session create/continue/stream, OAuth callbacks), request/response shapes, NDJSON event vocabulary, and dev-only runtime-artifact and schedule-dispatch endpoints.
- 24. [Troubleshooting](https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/24-troubleshooting.md) - Discovery diagnostics from eve info and eve build, .eve/discovery/diagnostics.json, common failure modes, dev server PID conflicts, and runtime error codes from failed steps/turns.

## Source File Index

- `.nvmrc`
- `apps/fixtures/agent-tui-client/agent/connections/stub-mcp-user.ts`
- `apps/fixtures/weather-agent/agent/agent.ts`
- `apps/fixtures/weather-agent/agent/instructions.md`
- `apps/fixtures/weather-agent/agent/skills/get-weather.md`
- `apps/fixtures/weather-agent/agent/tools/get_weather.ts`
- `docs/agent-config.md`
- `docs/channels/custom.mdx`
- `docs/channels/overview.mdx`
- `docs/concepts/context-control.md`
- `docs/concepts/default-harness.md`
- `docs/concepts/execution-model-and-durability.md`
- `docs/concepts/security-model.md`
- `docs/concepts/sessions-runs-and-streaming.md`
- `docs/connections.mdx`
- `docs/evals/overview.mdx`
- `docs/getting-started.mdx`
- `docs/guides/auth-and-route-protection.md`
- `docs/guides/client/overview.mdx`
- `docs/guides/client/streaming.mdx`
- `docs/guides/deployment.md`
- `docs/guides/dynamic-capabilities.md`
- `docs/guides/frontend/overview.mdx`
- `docs/guides/hooks.md`
- `docs/guides/instrumentation.md`
- `docs/guides/session-context.md`
- `docs/guides/state.md`
- `docs/instructions.mdx`
- `docs/introduction.md`
- `docs/README.md`
- `docs/reference/cli.md`
- `docs/reference/project-layout.md`
- `docs/reference/typescript-api.md`
- `docs/sandbox.mdx`
- `docs/schedules.mdx`
- `docs/skills.mdx`
- `docs/subagents.mdx`
- `docs/tools.mdx`
- `packages/eve/bin/eve.js`
- `packages/eve/package.json`
- `packages/eve/src/cli/commands/channels.ts`
- `packages/eve/src/cli/commands/deploy.ts`
- `packages/eve/src/cli/commands/info.ts`
- `packages/eve/src/cli/commands/init.ts`
- `packages/eve/src/cli/commands/link.ts`
- `packages/eve/src/cli/commands/register-project-commands.ts`
- `packages/eve/src/cli/dev/tui/setup-issues.ts`
- `packages/eve/src/cli/run.ts`
- `packages/eve/src/client/client.ts`
- `packages/eve/src/client/index.ts`
- `packages/eve/src/client/ndjson.ts`
- `packages/eve/src/client/open-stream.ts`
- `packages/eve/src/client/session.ts`
- `packages/eve/src/compiler/artifacts.ts`
- `packages/eve/src/compiler/channel-instrumentation-types.ts`
- `packages/eve/src/compiler/manifest.ts`
- `packages/eve/src/compiler/model-catalog.ts`
- `packages/eve/src/compiler/normalize-agent-config.ts`
- `packages/eve/src/compiler/normalize-channel.ts`
- `packages/eve/src/compiler/normalize-connection.ts`
- `packages/eve/src/compiler/normalize-hook.ts`
- `packages/eve/src/compiler/normalize-instructions.ts`
- `packages/eve/src/compiler/normalize-manifest.ts`
- `packages/eve/src/compiler/normalize-sandbox.ts`
- `packages/eve/src/compiler/normalize-schedule.ts`
- `packages/eve/src/compiler/normalize-skill.ts`
- `packages/eve/src/compiler/normalize-subagent.ts`
- `packages/eve/src/compiler/normalize-tool.ts`
- `packages/eve/src/compiler/workspace-resources.ts`
- `packages/eve/src/evals/cli/eval.ts`
- `packages/eve/src/evals/define-eval.ts`
- `packages/eve/src/execution/dispatch-runtime-actions-step.ts`
- `packages/eve/src/execution/durable-session-migrations/chain.ts`
- `packages/eve/src/execution/next-driver-action.ts`
- `packages/eve/src/execution/runtime-context.ts`
- `packages/eve/src/execution/runtime-errors.ts`
- `packages/eve/src/execution/sandbox/bash-tool.ts`
- `packages/eve/src/execution/sandbox/prewarm.ts`
- `packages/eve/src/execution/sandbox/read-file-tool.ts`
- `packages/eve/src/execution/skills/instructions.ts`
- `packages/eve/src/execution/subagent-adapter.ts`
- `packages/eve/src/execution/workflow-runtime.ts`
- `packages/eve/src/internal/nitro/host/configure-nitro-routes.ts`
- `packages/eve/src/internal/nitro/routes/agent-info/build-agent-info-response.ts`
- `packages/eve/src/internal/nitro/routes/channel-dispatch.ts`
- `packages/eve/src/internal/nitro/routes/dev-runtime-artifacts.ts`
- `packages/eve/src/internal/nitro/routes/dev-schedule-dispatch.ts`
- `packages/eve/src/internal/nitro/routes/health.ts`
- `packages/eve/src/internal/nitro/routes/index.ts`
- `packages/eve/src/internal/nitro/routes/info.ts`
- `packages/eve/src/protocol/routes.ts`
- `packages/eve/src/public/channels/index.ts`
- `packages/eve/src/public/connections/index.ts`
- `packages/eve/src/public/context/index.ts`
- `packages/eve/src/public/definitions/agent.ts`
- `packages/eve/src/public/definitions/hook.ts`
- `packages/eve/src/public/definitions/instructions.ts`
- `packages/eve/src/public/definitions/instrumentation.ts`
- `packages/eve/src/public/definitions/sandbox.ts`
- `packages/eve/src/public/definitions/tool.ts`
- `packages/eve/src/public/index.ts`
- `packages/eve/src/public/skills/index.ts`
- `packages/eve/src/public/tools/index.ts`
- `packages/eve/src/react/use-eve-agent.ts`
- `packages/eve/src/runtime/session-callback-route.ts`
- `README.md`

---

## 01. Overview

> Filesystem-first agent model, channel/harness/runtime split, public entry points (eve CLI, /eve/v1 HTTP routes, define* authoring), and the shortest path from init to a running session.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/01-overview.md
- Generated: 2026-06-16T19:23:02.387Z

### Source Files

- `README.md`
- `docs/introduction.md`
- `docs/README.md`
- `packages/eve/package.json`
- `packages/eve/src/public/index.ts`
- `packages/eve/src/protocol/routes.ts`

---
title: "Overview"
description: "Filesystem-first agent model, channel/harness/runtime split, public entry points (eve CLI, /eve/v1 HTTP routes, define* authoring), and the shortest path from init to a running session."
---

Eve is a filesystem-first framework for durable backend agents. You author capabilities as files under `agent/`, Eve compiles them into inspectable `.eve/` artifacts, and a Nitro-hosted runtime serves a stable `/eve/v1` HTTP protocol plus optional platform channels. The published npm package is `eve`; the CLI binary is `eve`. Node 24 or newer is required.

## Filesystem-first agent model

An Eve app is a TypeScript project whose agent contract is a directory tree, not a monolithic config object. Eve walks `agent/` at build time, derives names from paths (no `name` fields on `define*` calls), and serializes the discovered surface into `.eve/compile/compiled-agent-manifest.json` and `.eve/compile/module-map.mjs`.

:::files
```text
my-agent/
├── package.json
├── tsconfig.json
├── agent/
│   ├── agent.ts              # defineAgent — model, compaction, build
│   ├── instructions.md       # always-on system prompt (required on root)
│   ├── tools/                # defineTool integrations
│   ├── skills/               # on-demand procedures
│   ├── connections/          # MCP and OpenAPI connections
│   ├── sandbox/              # optional sandbox override + workspace seed
│   ├── channels/             # HTTP and messaging ingress (root-only)
│   ├── subagents/            # specialist child agents
│   ├── schedules/            # recurring jobs (root-only)
│   ├── hooks/                # stream-event subscribers
│   └── lib/                  # shared import-only code
└── evals/                    # defineEval suites (sibling of agent/)
```
:::

| Path | Resolves to | Author with |
| --- | --- | --- |
| `agent/tools/get_weather.ts` | tool `get_weather` | `defineTool` from `eve/tools` |
| `agent/connections/linear.ts` | connection `linear` | `defineMcpClientConnection` or `defineOpenAPIConnection` from `eve/connections` |
| `agent/skills/summarize.md` | skill `summarize` | markdown file (loaded via `load_skill`) |
| `agent/channels/slack.ts` | channel `slack` | `defineChannel` or platform factory from `eve/channels/*` |
| `agent/subagents/researcher/` | subagent `researcher` | nested `agent.ts` + authored slots |

The root agent name comes from `package.json` `name`, falling back to the app-root directory name. Local subagents take their name from their directory. Add a file and Eve discovers it; rename or move it and its identity moves with it. Run `eve info` to inspect the resolved surface and `.eve/discovery/diagnostics.json` when discovery fails.

<Info>
Only two authored sources reach the sandbox workspace at runtime: `agent/skills/` files seed to `/workspace/skills/...`, and `agent/sandbox/workspace/**` seeds to `/workspace/...` at session bootstrap. Everything in `lib/` stays import-only.
</Info>

## Channel, harness, and runtime

Eve separates transport, AI work, and durable orchestration. That split drives the public HTTP contract: channels own `continuationToken`; the runtime owns `sessionId` and the event stream.

```mermaid
flowchart TB
  subgraph ingress["Ingress (channels)"]
    HTTP["Eve HTTP channel<br/>/eve/v1/session"]
    Slack["Platform channels<br/>agent/channels/*"]
  end

  subgraph harness["Harness (one unit of AI work)"]
    Loop["Default agent loop"]
    Tools["Built-in + authored tools"]
    Model["Model call + compaction"]
  end

  subgraph runtime["Runtime (durable orchestration)"]
    WF["Workflow SDK checkpoints"]
    Stream["NDJSON event stream"]
    State["Session state + hooks"]
  end

  HTTP --> Loop
  Slack --> Loop
  Loop --> Tools
  Loop --> Model
  Loop --> WF
  WF --> Stream
  WF --> State
  Stream --> HTTP
  Stream --> Slack
```

| Layer | Responsibility | Owns |
| --- | --- | --- |
| **Channel** | Normalize inbound transport, apply auth and delivery policy, start or resume sessions | `continuationToken` |
| **Harness** | Run one unit of AI work (model step, tool calls, compaction) and return `{ session, next }` | Turn/step execution inside the agent loop |
| **Runtime** | Persist session state, follow `next`, stream events, orchestrate workflow primitives | `sessionId`, NDJSON stream, durable checkpoints |

Sessions nest as **session → turn → step**. Turns checkpoint at step boundaries via the Workflow SDK, so crash, timeout, and redeploy resume from the last completed step. Parked work (HITL approval, OAuth, subagents) suspends the workflow until input arrives. The default harness ships built-in tools (`bash`, file ops, `web_fetch`, `todo`, `ask_question`, `agent`, `load_skill`, `connection_search`) without any imports.

## Public entry points

Eve exposes three surfaces for authoring, operating, and integrating agents.

### `eve` CLI

The `eve` binary runs from the app root and loads `.env`/`.env.local` before every command. Running `eve` with no subcommand starts `eve dev`.

| Command | Purpose |
| --- | --- |
| `eve init [target]` | Scaffold a new agent or add one to an existing project |
| `eve info` | Print discovered tools, skills, routes, artifacts, and diagnostics |
| `eve build` | Compile `.eve/` artifacts and build host output |
| `eve start` | Serve the built `.output/` app |
| `eve dev` | Start local runtime and open the interactive terminal UI |
| `eve link` / `eve deploy` | Link to Vercel and deploy |
| `eve eval` | Run evals locally or against a remote URL |
| `eve channels add/list` | Scaffold or list authored channels |

Recommended loop: edit authored files → `eve info` to verify discovery → `eve dev` to iterate → `eve build` → `eve start` for production.

### `/eve/v1` HTTP routes

All stable runtime transport routes share the `EVE_ROUTE_PREFIX` of `/eve/v1`.

| Method | Route | Purpose |
| --- | --- | --- |
| `GET` | `/eve/v1/health` | Health check |
| `GET` | `/eve/v1/info` | JSON inspection payload for the current agent |
| `POST` | `/eve/v1/session` | Create a new durable session |
| `POST` | `/eve/v1/session/:sessionId` | Continue a session with `continuationToken` |
| `GET` | `/eve/v1/session/:sessionId/stream` | NDJSON event stream |
| `GET` | `/eve/v1/connections/:name/callback/:token` | OAuth callback (unguessable token) |
| `GET` | `/eve/v1/callback/:token` | Terminal session callback |

Dev-only routes (`/eve/v1/dev/schedules/:scheduleId`, `/eve/v1/dev/runtime-artifacts`) register only when Nitro runs in dev mode.

:::endpoint POST /eve/v1/session
Create a durable session. Returns `sessionId` and `continuationToken` in the JSON body; the `x-eve-session-id` header names the session to stream.
:::

### `define*` authoring API

Authoring helpers live in `packages/eve/src/public/` and ship through the `eve` package exports.

| Import path | Helper | Authored slot |
| --- | --- | --- |
| `eve` | `defineAgent`, `defineRemoteAgent` | `agent/agent.ts` |
| `eve/instructions` | `defineInstructions`, `defineDynamic` | `agent/instructions.ts` or `agent/instructions/` |
| `eve/tools` | `defineTool`, `defineBashTool`, `defineReadFileTool`, … | `agent/tools/*.ts` |
| `eve/skills` | `defineDynamic` | dynamic skill modules |
| `eve/connections` | `defineMcpClientConnection`, `defineOpenAPIConnection` | `agent/connections/*.ts` |
| `eve/channels` | `defineChannel` | `agent/channels/*.ts` |
| `eve/sandbox` | `defineSandbox` | `agent/sandbox.ts` or `agent/sandbox/sandbox.ts` |
| `eve/schedules` | `defineSchedule` | `agent/schedules/*.ts` or `*.md` |
| `eve/hooks` | `defineHook` | `agent/hooks/*.ts` |
| `eve/context` | `defineState` | session-scoped durable state |
| `eve/instrumentation` | `defineInstrumentation` | `agent/instrumentation.ts` (root-only) |
| `eve/evals` | `defineEval`, `defineEvalConfig` | `evals/` at app root |

Tool and connection handlers receive a `ctx` with `ctx.session`, `ctx.getSandbox()`, `ctx.getSkill()`, `ctx.getToken()`, and `ctx.requireAuth()`. Client integration ships separately via `eve/client`, `eve/react`, `eve/vue`, `eve/svelte`, and framework plugins `eve/next`, `eve/nuxt`, `eve/sveltekit`.

## Shortest path to a running session

<Steps>
<Step title="Scaffold">

```bash
npx eve@latest init my-agent
```

Creates the project, installs `eve`, `ai`, and `zod`, initializes Git, and starts `eve dev` with the interactive terminal UI. Stop with Ctrl+C before editing. Pass `--channel-web-nextjs` to add the Web Chat application.

</Step>

<Step title="Verify discovery">

```bash
cd my-agent
npx eve info
```

Confirms `instructions.md`, `agent.ts`, the built-in HTTP channel, and compiled artifact paths under `.eve/`.

</Step>

<Step title="Run locally">

```bash
npx eve dev
```

Starts the dev server (default port 2000) and opens the terminal UI. Set a model credential before prompting: gateway model ids need `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN`; direct provider ids need the matching provider key (for example `ANTHROPIC_API_KEY` for `anthropic/...`).

</Step>

<Step title="Create and stream a session">

<RequestExample>
```bash
curl -X POST http://127.0.0.1:2000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Hello"}'
```
</RequestExample>

<ResponseExample>
```json
{
  "sessionId": "ses_…",
  "continuationToken": "ct_…"
}
```
</ResponseExample>

Attach to the stream:

```bash
curl http://127.0.0.1:2000/eve/v1/session/<sessionId>/stream
```

The response is NDJSON (`application/x-ndjson`). Expect lifecycle events such as `session.started`, `turn.started`, `step.started`, `message.completed`, and `session.waiting` or `session.completed`.

</Step>

<Step title="Send a follow-up">

When the session parks (`session.waiting`), POST the next message with the stored token:

```bash
curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId> \
  -H 'content-type: application/json' \
  -d '{"continuationToken":"<token>","message":"Follow up"}'
```

</Step>
</Steps>

<Warning>
`continuationToken` is a resume handle for the session's current workflow hook, not a general message-queue address. Send one user turn at a time and wait for `session.waiting` before delivering the next message to the same session.
</Warning>

## Compiled artifacts

`eve build` and `eve dev` write inspectable output under `.eve/`:

| Artifact | Contents |
| --- | --- |
| `.eve/discovery/agent-discovery-manifest.json` | Filesystem discovery results |
| `.eve/discovery/diagnostics.json` | Authored-shape errors and warnings |
| `.eve/compile/compiled-agent-manifest.json` | Serialized surface Eve loads at runtime |
| `.eve/compile/module-map.mjs` | Compiled module entrypoints |

On Vercel (`VERCEL=1`), `eve build` also writes `.vercel/output`. Local `eve build` skips that bundle but still produces `.eve/` artifacts.

## Next

<CardGroup>
<Card title="Installation" href="/installation">
Node 24+ prerequisites, install paths, model credentials, and environment file loading.
</Card>
<Card title="Quickstart" href="/quickstart">
Scaffold, verify with `eve info`, iterate with `eve dev`, and exercise `/eve/v1/session`.
</Card>
<Card title="Project layout" href="/project-layout">
Every authored slot under `agent/`, path-derived naming, and what compiles into `.eve/`.
</Card>
<Card title="Execution model" href="/execution-model">
Session/turn/step nesting, durable checkpoints, and parked work semantics.
</Card>
<Card title="Sessions and streaming" href="/sessions-and-streaming">
`continuationToken` vs `sessionId`, NDJSON events, and reconnect behavior.
</Card>
<Card title="CLI reference" href="/cli-reference">
All `eve` commands, flags, and the edit-info-dev-build-start loop.
</Card>
</CardGroup>

---

## 02. Installation

> Node 24+ prerequisites, npm/pnpm install paths, eve package binary, model credential options (gateway OIDC or direct provider keys), and environment file loading from the app root.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/02-installation.md
- Generated: 2026-06-16T19:23:37.228Z

### Source Files

- `docs/getting-started.mdx`
- `packages/eve/package.json`
- `packages/eve/bin/eve.js`
- `.nvmrc`
- `packages/eve/src/cli/run.ts`

---
title: "Installation"
description: "Node 24+ prerequisites, npm/pnpm install paths, eve package binary, model credential options (gateway OIDC or direct provider keys), and environment file loading from the app root."
---

Install Eve as the `eve` npm package. The published binary scaffolds agents, compiles authored files under `agent/`, and runs a local development server. Before your first session, you need a compatible Node.js runtime, the package on disk, and a model credential.

## Prerequisites

Eve requires **Node.js 24 or newer**. The `eve` package declares `engines.node` as `>=24`, and the repository pins `.nvmrc` to `24`. The CLI binary checks your Node version on every invocation and exits with an error if the running version is outside the supported range.

<Tabs>
<Tab title="nvm">

```bash
nvm install 24
nvm use 24
node --version
```

</Tab>
<Tab title="fnm">

```bash
fnm install 24
fnm use 24
node --version
```

</Tab>
<Tab title="Direct install">

Download Node 24+ from [nodejs.org](https://nodejs.org/) or use your system package manager, then verify:

```bash
node --version
```

</Tab>
</Tabs>

You also need a **model credential** before the agent can call a model. Eve supports gateway credentials (API key or Vercel OIDC) and direct provider API keys. See [Model credentials](#model-credentials) below.

## Install paths

Pick the path that matches your starting point: a greenfield agent, an existing Node project, or a manual wiring.

<Steps>
<Step title="Scaffold a new agent (recommended)">

`npx` runs `eve init` without installing Eve globally first:

```bash
npx eve@latest init my-agent
```

The command creates a new directory, writes `agent/agent.ts` and `agent/instructions.md`, installs dependencies, initializes Git, and starts `eve dev`. Press **Ctrl+C** to stop the dev server and return to your shell before editing files.

Fresh scaffolds use the package manager that launched the CLI (`npx` → npm, `pnpm dlx` → pnpm). When you run the `eve` binary directly with no package-runner context, Eve defaults to **pnpm**.

</Step>
<Step title="Add Eve to an existing project">

From a directory that already has `package.json` and no `agent/` tree yet:

```bash
npx eve@latest init .
```

Eve adds the `agent/` files and missing dependencies (`eve`, `ai`, `zod`) without modifying unrelated project configuration. It detects the project's existing package manager from `package.json#packageManager` or lockfiles (`pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lock`).

If the project's `engines.node` is incompatible with Eve's required major, Eve replaces it with a pinned range such as `24.x` and prints a warning.

</Step>
<Step title="Install dependencies manually">

For full control, declare a compatible Node runtime and install the runtime dependencies yourself:

```json title="package.json"
{
  "engines": {
    "node": "24.x"
  }
}
```

<CodeGroup>
```bash title="npm"
npm install eve@latest ai zod
```

```bash title="pnpm"
pnpm add eve@latest ai zod
```

```bash title="yarn"
yarn add eve@latest ai zod
```

```bash title="bun"
bun add eve@latest ai zod
```
</CodeGroup>

Then author the two files the runtime needs:

```md title="agent/instructions.md"
You are a concise assistant. Use tools when they are available.
```

```ts title="agent/agent.ts"
import { defineAgent } from "eve";

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

Run the dev server with `npx eve dev` or add a `"dev": "eve dev"` script to `package.json`.

</Step>
</Steps>

### What gets installed

A scaffolded agent declares these runtime dependencies:

| Package | Role |
| --- | --- |
| `eve` | Framework, CLI binary, compiled runtime |
| `ai` | AI SDK model calls and streaming |
| `zod` | Tool and schema validation |
| `@vercel/connect` | Connection helpers (scaffold only) |

The `eve` package ships with a single runtime dependency (`nitro`). Peer dependencies such as framework plugins (`next`, `nuxt`, `sveltekit`) and optional integrations are installed only when you add them.

Once `eve` is installed, the full documentation bundle is available locally at `node_modules/eve/docs/`.

## The `eve` binary

The npm package exposes a CLI binary mapped to `bin/eve.js`:

<ParamField body="bin" type="string">
Maps to `./bin/eve.js` in the published `eve` package.
</ParamField>

<ParamField body="default command" type="string">
Running `eve` with no subcommand runs `eve dev`.
</ParamField>

The bootstrap script validates Node.js compatibility, ensures the compiled CLI entrypoint exists (rebuilding from source when developing inside the Eve monorepo), and delegates to `dist/src/cli/run.js`. Available commands include `init`, `info`, `build`, `start`, `dev`, `link`, `deploy`, `eval`, and `channels`.

Scaffolded projects wire the binary into `package.json` scripts:

```json
{
  "scripts": {
    "build": "eve build",
    "dev": "eve dev",
    "start": "eve start"
  }
}
```

Every CLI command resolves the **application root** from the current working directory. Run commands from your agent project root so discovery, env loading, and artifact paths resolve correctly.

## Model credentials

Eve routes models through the **Vercel AI Gateway** by default. A bare string model id such as `anthropic/claude-sonnet-4.6` (the `eve init` default) is classified as gateway-routed. Alternatively, you can use a direct AI SDK provider instance that bypasses the gateway.

### Gateway credentials

Gateway-routed models need one of two credentials:

| Credential | Environment variable | How to obtain |
| --- | --- | --- |
| API key | `AI_GATEWAY_API_KEY` | Create a key in the [Vercel AI Gateway dashboard](https://vercel.com/dashboard/ai/api-keys), or paste one in the dev TUI `/model` flow |
| OIDC token | `VERCEL_OIDC_TOKEN` | Run `eve link` to link a Vercel project and pull preview environment variables into `.env.local` |

When both are present, **`AI_GATEWAY_API_KEY` takes precedence** over `VERCEL_OIDC_TOKEN`, matching the AI SDK gateway provider behavior.

<Tabs>
<Tab title="API key">

Create or copy an AI Gateway API key, then add it to `.env.local` at the app root:

```bash title=".env.local"
AI_GATEWAY_API_KEY=your-gateway-key
```

</Tab>
<Tab title="Vercel OIDC">

From an interactive terminal in your agent project:

```bash
eve link
```

The command walks you through team and project selection, runs `vercel link`, and pulls environment variables so a gateway credential lands in `.env.local`. A running `eve dev` reloads env files automatically — no restart needed.

In CI, use `vercel link --project <name> --yes` instead; `eve link` requires an interactive terminal.

</Tab>
</Tabs>

The dev TUI surfaces credential status in its status bar and gates the "provider required" prompt when neither credential is detectable. Use `/model` to walk through provider setup interactively.

### Direct provider keys

For models that bypass the gateway, set the provider's own API key. The environment variable name follows the model id prefix:

| Model id pattern | Environment variable |
| --- | --- |
| `anthropic/claude-...` | `ANTHROPIC_API_KEY` |
| `openai/gpt-...` | `OPENAI_API_KEY` |
| `google/gemini-...` | `GOOGLE_API_KEY` |

The prefix before the first `/` determines the variable name: uppercase slug with `_API_KEY` appended (for example, `anthropic/claude-sonnet-4.6` → `ANTHROPIC_API_KEY`).

To use a direct provider SDK model instead of a gateway string id, change `agent/agent.ts`:

```ts title="agent/agent.ts"
import { anthropic } from "@ai-sdk/anthropic";
import { defineAgent } from "eve";

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

Direct provider instances are classified as `external` routing. Gateway credentials do not apply; the provider key must be present in the environment. Eve makes no connectedness claim for external providers on `/eve/v1/info`.

### Bring-your-own-key through the gateway

The scaffold can also wire a provider key through the gateway's `byok` block in `modelOptions.providerOptions.gateway.byok`. This passes your provider API key to the gateway explicitly while keeping a gateway model id string in `model`.

## Environment file loading

Local CLI commands load environment files from the **application root** (the directory where you run `eve`). Commands that load env files include `eve dev`, `eve build`, `eve start`, and `eve eval`.

### File precedence

Eve reads up to four files, listed here from **highest to lowest** precedence:

1. `.env.development.local`
2. `.env.local`
3. `.env.development`
4. `.env`

Later files in this list override earlier ones for the same key. Values from `.env.local` typically hold secrets such as `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN`.

### Shell precedence

Variables already set in the parent process (your shell) are **protected**. Env files never overwrite shell-exported values. This lets you override project secrets temporarily without editing files.

### Hot reload

During `eve dev`, Eve watches env files for changes and reloads them without restarting the server. After `eve link` pulls new credentials or you edit `.env.local`, the running dev server picks up the updated values automatically.

Missing env files are silently skipped; only files that exist on disk are parsed.

## Verify installation

<Steps>
<Step title="Check Node version">

```bash
node --version
```

Expect v24.x or newer.

</Step>
<Step title="Confirm the binary">

```bash
npx eve --version
```

Prints the installed `eve` package version.

</Step>
<Step title="Inspect discovery">

From your agent project root:

```bash
npx eve info
```

Confirms Eve discovered `agent/agent.ts`, tools, channels, and routes. Use `--json` for machine-readable output.

</Step>
<Step title="Start the dev server">

```bash
npm run dev
```

Or, without a `dev` script:

```bash
npx eve dev
```

If credentials are missing, the dev TUI flags the gap and `/model` walks you through setup. With credentials in place, type a message to confirm the model loop runs.

</Step>
</Steps>

### Common issues

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| `Eve requires Node.js >=24` | Node version too old | Install Node 24+ and retry |
| Gateway "no credentials" error | Missing `AI_GATEWAY_API_KEY` and `VERCEL_OIDC_TOKEN` | Run `eve link` or set `AI_GATEWAY_API_KEY` in `.env.local` |
| Stale gateway key shadows OIDC | Shell exports an old `AI_GATEWAY_API_KEY` | Unset the shell variable or update the key |
| Provider key missing for direct model | External provider without env var | Set the provider's `*_API_KEY` in `.env.local` |
| Env changes not picked up | Running outside `eve dev` | Restart the process, or use `eve dev` which hot-reloads env files |

For deeper diagnostics, see [Troubleshooting](/troubleshooting).

## Next

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Scaffold with `eve init`, verify with `eve info`, iterate with `eve dev`, and exercise the HTTP session API.
</Card>
<Card title="Project layout" href="/project-layout">
Where authored files live under `agent/` and what compiles into `.eve/` artifacts.
</Card>
<Card title="Agent configuration" href="/agent-configuration">
Configure `defineAgent` model, compaction, and build options in `agent/agent.ts`.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Route auth, Vercel secrets, `eve link`/`eve deploy`, and production verification.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full command reference for `init`, `info`, `build`, `start`, `dev`, `link`, `deploy`, and `eval`.
</Card>
</CardGroup>

---

## 03. Quickstart

> Scaffold with eve init, verify discovery with eve info, iterate with eve dev, and create/stream/continue an HTTP session against /eve/v1/session.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/03-quickstart.md
- Generated: 2026-06-16T19:23:43.601Z

### Source Files

- `docs/getting-started.mdx`
- `packages/eve/src/cli/commands/init.ts`
- `apps/fixtures/weather-agent/agent/agent.ts`
- `apps/fixtures/weather-agent/agent/tools/get_weather.ts`
- `apps/fixtures/weather-agent/agent/instructions.md`
- `packages/eve/src/protocol/routes.ts`

---
title: "Quickstart"
description: "Scaffold with eve init, verify discovery with eve info, iterate with eve dev, and create/stream/continue an HTTP session against /eve/v1/session."
---

The shortest path from zero to a running agent is four CLI surfaces and one HTTP contract: `eve init` writes `agent/` and installs dependencies, `eve info` reports compiled discovery and the active message routes, `eve dev` starts the Nitro dev host (default port `2000`, overridable via `$PORT` or `--port`), and the built-in Eve channel at `agent/channels/eve.ts` serves `POST /eve/v1/session`, `POST /eve/v1/session/:sessionId`, and `GET /eve/v1/session/:sessionId/stream`.

## Prerequisites

<Note>
See [Installation](/installation) for Node version requirements, package-manager install paths, and model credential setup.
</Note>

- Node 24 or newer
- A package manager (`npm`, `pnpm`, `yarn`, or `bun`)
- A model credential for the scaffold's default model (`anthropic/claude-sonnet-4.6`)

Gateway-routed model ids need `AI_GATEWAY_API_KEY` or a `VERCEL_OIDC_TOKEN` from `vercel link`. Direct provider ids need that provider's key (for example, `anthropic/claude-...` → `ANTHROPIC_API_KEY`). Place keys in `.env.local` or another file from Eve's development env load order (`.env.development.local` → `.env.local` → `.env.development` → `.env`).

## Scaffold an agent

<Steps>
<Step title="Create a new project">

Run `eve init` without a global install:

```bash
npx eve@latest init my-agent
cd my-agent
```

The command:

- Creates a child directory with `agent/agent.ts`, `agent/instructions.md`, and `agent/channels/eve.ts`
- Pins `eve`, `ai`, `zod`, and `@vercel/connect` in `package.json`
- Adds `dev`, `build`, and `start` scripts that call the `eve` binary
- Installs dependencies and initializes Git (fresh scaffolds only)
- Starts `eve dev --input /model` in an interactive terminal (unless a coding agent launched the CLI)

Pass `--channel-web-nextjs` to also scaffold a Next.js Web Chat app. Every scaffold ships the HTTP channel regardless.

<Warning>
`eve init` holds the terminal while the dev TUI runs. Press Ctrl+C to return to your shell before editing files. The command does not create a Vercel project or deploy.
</Warning>

</Step>

<Step title="Or add to an existing project">

From a directory that already has `package.json` and no conflicting `agent/` files:

```bash
npx eve@latest init .
```

Eve writes only the `agent/` tree and adds missing runtime dependencies. It does not modify unrelated host configuration. `--channel-web-nextjs` is not supported for in-place init; run `eve channels add web` afterward instead.

</Step>
</Steps>

### Scaffold layout

:::files
my-agent/
├── agent/
│   ├── agent.ts          # defineAgent({ model })
│   ├── instructions.md   # always-on system prompt
│   └── channels/
│       └── eve.ts        # HTTP ingress (POST/GET /eve/v1/session*)
├── package.json          # dev / build / start scripts
├── tsconfig.json
└── .gitignore            # ignores .eve, .env*, node_modules
:::

The default `agent/agent.ts` sets `model: "anthropic/claude-sonnet-4.6"`. Instructions start as a minimal markdown prompt. The default harness already exposes file, shell, web, and delegation tools before you author any custom tools.

## Verify discovery

Run `eve info` from the app root after scaffolding or any edit under `agent/`:

```bash
npx eve info
```

Human output reports app paths, compile status, discovery diagnostics (errors and warnings), discovered instructions/skills/tools/channels, and the active messaging contract:

| Label | Example value |
| --- | --- |
| Create | `POST /eve/v1/session` |
| Continue | `POST /eve/v1/session/:sessionId` |
| Stream | `GET /eve/v1/session/:sessionId/stream` |

For machine-readable output:

```bash
npx eve info --json
```

The JSON document includes `status`, `model`, `tools`, `channels`, `messaging` route paths, and `.eve/` artifact paths (`compiledManifest`, `discoveryManifest`, `diagnostics`, `moduleMap`, `metadata`). Fix discovery errors before starting the dev server; diagnostics also land in `.eve/discovery/diagnostics.json` after compile.

<Check>
A healthy quickstart run shows compile status `ready` and zero discovery errors.
</Check>

## Add a first tool

Tool names are derived from filenames under `agent/tools/` — no `name` field. Use snake_case ASCII. Create `agent/tools/get_weather.ts`:

```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 }) {
    return { city, condition: "Sunny", temperatureF: 72 };
  },
});
```

Tools execute in the app runtime with access to `process.env`, not inside the sandbox. Re-run `eve info` to confirm `get_weather` appears in the tools list.

## Run locally

From the app root:

```bash
npm run dev
```

Equivalent direct invocation:

```bash
npx eve dev
```

<ParamField body="--port" type="number">
Listen port. Defaults to `$PORT`, then `2000`.
</ParamField>

<ParamField body="--no-ui" type="boolean">
Start the server without the interactive terminal UI.
</ParamField>

<ParamField body="--url" type="string">
Connect the TUI to an existing server URL instead of starting a local host (for example, `eve dev https://your-app.vercel.app`).
</ParamField>

The dev server loads environment files from the app root, compiles `agent/` into `.eve/`, and serves the Eve channel. In the TUI, type a message and observe tool calls, results, and the assistant reply in order.

<Tip>
Recommended iteration loop: edit `agent/` → `eve info` → `eve dev` → `eve build` → `eve start` for production-shaped verification.
</Tip>

## Create an HTTP session

Every Eve app exposes the same stable routes through the built-in Eve channel. With the dev server running on port `2000`:

:::endpoint POST /eve/v1/session
Create a durable session and enqueue the first user turn.
:::

<ParamField body="message" type="string | UserContent" required>
Plain text or multimodal AI SDK user content.
</ParamField>

<ParamField body="clientContext" type="string | string[] | object">
One-turn client context converted into model context by the channel.
</ParamField>

<ParamField body="outputSchema" type="object">
JSON schema for structured assistant output on this turn.
</ParamField>

<RequestExample>

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

</RequestExample>

<ResponseExample>

```json
{
  "continuationToken": "eve:…",
  "ok": true,
  "sessionId": "session_…"
}
```

Response headers include `x-eve-session-id` (same value as `sessionId`). Status is `202 Accepted`.

</ResponseExample>

<ResponseField name="continuationToken" type="string">
Namespaced token required to continue the conversation. Store it with `sessionId`.
</ResponseField>

<ResponseField name="sessionId" type="string">
Durable session identifier for stream attachment and follow-up posts.
</ResponseField>

Local development accepts loopback requests without extra auth headers. Deployed targets use the Eve channel's route-auth chain (see [Security model](/security-model)).

## Stream the session

:::endpoint GET /eve/v1/session/:sessionId/stream
Attach to the durable NDJSON event stream for one session.
:::

<ParamField query="startIndex" type="number">
Non-negative event index to resume from. Omit to receive the full stream from the beginning.
</ParamField>

<RequestExample>

```bash
curl -N http://127.0.0.1:2000/eve/v1/session/<sessionId>/stream
```

</RequestExample>

The response uses `Content-Type: application/x-ndjson; charset=utf-8` with `x-eve-stream-format: ndjson` and `x-eve-stream-version: 15`.

A tool-using run typically emits:

| Event | When |
| --- | --- |
| `session.started` | Workflow session begins |
| `turn.started` | Turn boundary |
| `message.received` | User message accepted |
| `actions.requested` | Harness requests tool calls (for example `get_weather`) |
| `action.result` | Tool result projected back |
| `message.completed` | Final assistant text for the step |
| `session.waiting` | Session parked for the next user message |
| `session.completed` | Terminal success |

`message.appended` and `reasoning.appended` are optional incremental events; clients that only need final text can wait for `message.completed`. Human-in-the-loop, authorization, and failure events (`input.requested`, `authorization.required`, `turn.failed`, `session.failed`) appear when the harness parks for external input.

```mermaid
sequenceDiagram
  participant Client
  participant EveChannel as agent/channels/eve.ts
  participant Runtime as Durable workflow

  Client->>EveChannel: POST /eve/v1/session {message}
  EveChannel->>Runtime: send(turn, continuationToken)
  EveChannel-->>Client: 202 {sessionId, continuationToken}
  Client->>EveChannel: GET /eve/v1/session/:id/stream
  Runtime-->>EveChannel: NDJSON events
  EveChannel-->>Client: session.started … message.completed
  Client->>EveChannel: POST /eve/v1/session/:id {continuationToken, message}
  EveChannel->>Runtime: send(follow-up)
  EveChannel-->>Client: 200 {ok, sessionId}
```

## Continue the conversation

When the stream shows `session.waiting` (or after `message.completed` on the first turn), post a follow-up:

:::endpoint POST /eve/v1/session/:sessionId
Deliver the next user message or HITL input to an existing session.
:::

<ParamField body="continuationToken" type="string" required>
Token from the create response (or the latest turn boundary). Requests without it return `400`.
</ParamField>

<ParamField body="message" type="string | UserContent">
Next user message. Required unless `inputResponses` is supplied.
</ParamField>

<ParamField body="inputResponses" type="InputResponse[]">
Human-in-the-loop responses when the harness emitted `input.requested`.
</ParamField>

<RequestExample>

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

</RequestExample>

<ResponseExample>

```json
{
  "ok": true,
  "sessionId": "session_…"
}
```

</ResponseExample>

Re-attach to the stream (same `sessionId`) to observe the next turn. The continue route returns `200`; it does not echo a new `continuationToken` in the JSON body — reuse the token from session creation until the harness re-keys it at a turn boundary.

## What you have now

| Surface | Role |
| --- | --- |
| `agent/instructions.md` | Always-on behavior |
| `agent/tools/*.ts` | Typed capabilities the model invokes |
| `agent/channels/eve.ts` | HTTP ingress for sessions |
| `eve dev` | Local host + optional TUI |
| `/eve/v1/session*` | Create, stream, and continue durable conversations |

The agent already runs with the default harness (bash, file ops, `web_fetch`, `todo`, `ask_question`, `agent`, `load_skill`, `connection_search`). Override or disable built-ins under `agent/tools/` when you need tighter control.

## Next

<CardGroup>
<Card title="Project layout" href="/project-layout">
Authored slots under `agent/`, path-derived naming, and what compiles into `.eve/`.
</Card>
<Card title="Sessions and streaming" href="/sessions-and-streaming">
`continuationToken` contracts, NDJSON vocabulary, reconnect behavior, and subagent streams.
</Card>
<Card title="Default harness" href="/default-harness">
Built-in tools, compaction defaults, and override patterns.
</Card>
<Card title="Client integration" href="/client-integration">
`eve/client` `Client` and `ClientSession`, plus React/Vue/Svelte hooks.
</Card>
<Card title="HTTP API reference" href="/http-api">
Full `/eve/v1` route inventory, request shapes, and error codes.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Discovery diagnostics, dev-server conflicts, and runtime failure codes.
</Card>
</CardGroup>

---

## 04. Project layout

> Authored slots under agent/, path-derived naming (no name fields), evals/ placement, subagent inheritance rules, and what compiles into .eve/ artifacts.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/04-project-layout.md
- Generated: 2026-06-16T19:23:18.457Z

### Source Files

- `docs/reference/project-layout.md`
- `packages/eve/src/compiler/manifest.ts`
- `packages/eve/src/compiler/normalize-manifest.ts`
- `packages/eve/src/compiler/artifacts.ts`
- `packages/eve/src/cli/commands/info.ts`

---
title: "Project layout"
description: "Authored slots under agent/, path-derived naming (no name fields), evals/ placement, subagent inheritance rules, and what compiles into .eve/ artifacts."
---

Eve compiles an agent by walking the filesystem under `agent/`. Each top-level directory or file is an authored slot; the slot a file lands in determines how discovery loads it, and the compiler normalizes the result into versioned artifacts under `.eve/`. Run `eve info` or `eve build` to inspect what was discovered and where artifacts were written.

## App root and agent root

Discovery resolves two roots from your working directory:

| Root | Role |
| --- | --- |
| **App root** | The enclosing project directory (marked by `package.json` or `vercel.json`) |
| **Agent root** | The directory Eve walks for authored slots |

Eve supports two layouts:

| Layout | Agent root | App root | When to use |
| --- | --- | --- | --- |
| **Nested** (recommended) | `<app-root>/agent/` | Parent of `agent/` | Keeps app scaffolding separate from the authored surface |
| **Flat** | Same as app root | Same directory | Supported when the app root is also the agent root |

Nested layout is detected when the app root contains an `agent/` directory. Flat layout is detected when the current directory contains recognized agent-root entries (`agent.ts`, `instructions.md`, `tools/`, and similar) without a separate `agent/` folder.

<Note>
`eve info` reports `layout` as `nested` or `flat`, plus resolved `appRoot` and `agentRoot` paths.
</Note>

## Path-derived naming

Identity comes from the path. Authored `define*` exports do not carry `name` or `id` fields — the compiler derives identifiers from filenames and directory names.

| Authored path | Runtime identifier |
| --- | --- |
| `agent/tools/get_weather.ts` | tool `get_weather` |
| `agent/tools/billing/refund.ts` | tool `billing-refund` (nested segments flattened with `-`) |
| `agent/connections/linear.ts` | connection `linear` |
| `agent/skills/summarize.md` | skill `summarize` |
| `agent/subagents/researcher/agent.ts` | subagent tool `researcher` |
| `agent/subagents/linear.ts` (with `defineRemoteAgent`) | remote subagent tool `linear` |

**Root agent name** comes from `package.json` `name` (npm scope stripped), falling back to the app-root directory basename when `name` is absent.

**Local subagent name** comes from the directory name (`subagents/<id>/`) or the module basename for single-file subagents (`subagents/<id>.ts`).

Subagent tool names share the same runtime namespace as authored tools. A subagent named `researcher` collides with a tool named `researcher`; Eve rejects the build rather than picking a winner.

## Recommended layout

:::files
my-agent/
├── package.json
├── tsconfig.json
├── agent/
│   ├── agent.ts
│   ├── instructions.md
│   ├── instrumentation.ts
│   ├── channels/
│   ├── connections/
│   ├── hooks/
│   ├── skills/
│   ├── lib/
│   ├── sandbox/
│   ├── tools/
│   ├── schedules/
│   └── subagents/
└── evals/
    ├── evals.config.ts
    └── smoke.eval.ts
:::

`evals/` lives at the app root as a sibling of `agent/`, not inside it. Eval files are discovered at `eve eval` time and are not written into `.eve/`.

## Authored slot table

The **Subagents** column states whether a local subagent package (`subagents/<id>/`) may author the slot. Unsupported directories under an agent root are ignored with a discovery warning.

| Path | Description | Subagents | Notes |
| --- | --- | --- | --- |
| `agent.ts` | Runtime config (`defineAgent`) | Yes | Model, compaction, `build.externalDependencies`, `experimental`. Required `description` on subagents. |
| `instructions.md` / `instructions.ts` / `instructions/` | Base system prompt | Optional | Flat file or directory of `.md`/`.ts` sources. Static sources compose at build time; `defineDynamic` + `defineInstructions` resolve at runtime. **Required on root**, optional on subagents. |
| `instrumentation.ts` | Telemetry config | No | OTel exporter and AI SDK span settings. Auto-discovered at server startup before agent code. Root-only. |
| `channels/` | HTTP and messaging entrypoints | No | Module-backed only. Recursive directories supported. Root-only. |
| `connections/` | MCP and OpenAPI connections | Yes | One connection per file or folder (`connections/<name>/connection.ts`). Name derived from path segment. |
| `hooks/` | Lifecycle and stream-event subscribers | Yes | Module-backed only. Recursive directories supported. |
| `skills/` | On-demand procedures and capability packs | Yes | Flat markdown, module-backed skills, or packaged skills. Seeded into `/workspace/skills/...`. |
| `lib/` | Shared authored helper code | Yes | Import-only; never mounted into the sandbox workspace. |
| `sandbox.ts` or `sandbox/sandbox.ts` | Single sandbox definition | Yes | Top-level `sandbox.ts` for definition-only override; `sandbox/sandbox.ts` + `sandbox/workspace/**` to also seed files. Framework default applies when neither is authored. |
| `sandbox/workspace/**` | Files seeded into the sandbox | Yes | Mirrored into `/workspace/...` at session bootstrap. Cannot contain `skills/` (reserved). |
| `tools/` | Typed executable integrations | Yes | Module-backed only. Recursive directories supported; nested paths flatten into tool names. |
| `schedules/` | Recurring jobs | No | `<name>.ts` (`defineSchedule`) or `<name>.md` (frontmatter `cron:` + prompt body). Recursive nesting supported. Root-only. |
| `subagents/` | Specialist child agents | Yes | Each child is a local package under `subagents/<id>/`, a single-file module, or a `defineRemoteAgent` module. Nested subagents supported. |

<Tip>
Legacy `system.md` / `system.ts` still resolve with a deprecation warning. Prefer `instructions.*`.
</Tip>

## What reaches the sandbox workspace

Eve does not mount the entire `agent/` tree into the sandbox. Only two authored sources land in the live workspace:

| Authored source | Sandbox path |
| --- | --- |
| `agent/skills/**` | `/workspace/skills/...` |
| `agent/sandbox/workspace/**` | `/workspace/...` at session bootstrap |

Everything under `lib/` stays import-only source code and never reaches the workspace. The compiled manifest records a `workspaceResourceRoot` descriptor pointing at materialized trees under `.eve/compile/workspace-resources/`.

## Local subagent layout and inheritance

A declared local subagent lives under `agent/subagents/<id>/` and uses the same slot grammar as the root, minus root-only slots.

:::files
agent/subagents/researcher/
├── agent.ts              # required; must export description
├── instructions.md       # optional
├── connections/
├── hooks/
├── skills/
├── lib/
├── sandbox/
├── tools/
└── subagents/            # nested subagents supported
:::

**Inheritance rule:** A declared subagent inherits nothing from the root's authored slots. Discovery treats its directory as its own agent root. An absent slot falls back to the framework default, not to the root's version.

| Slot | Declared subagent behavior |
| --- | --- |
| Instructions | Own `instructions.{md,ts}` or directory; optional |
| Tools, connections, skills, hooks | Own directories only |
| Sandbox | Own `sandbox.ts` or `sandbox/`; else framework default (not parent's sandbox) |
| Channels, schedules, instrumentation | Not supported — discovery emits an error for `schedules/` inside subagents |
| State (`defineState`) | Fresh per child session; never shared |

When two subagents need the same procedure, copy skill markdown into each `skills/` directory, or share typed helpers via `lib/`. The built-in `agent` tool is the exception: its children are copies of the parent and share sandbox, tools, and connections.

Remote subagents (`defineRemoteAgent`) also live under `agent/subagents/` as single-file modules. Identity is path-derived like local subagents; the compiler lowers them into the manifest's `remoteAgents` array.

## Evals placement

Evals are authored at the app root, outside `agent/`:

| File pattern | Purpose |
| --- | --- |
| `evals/**/*.eval.ts` | Eval definitions (`defineEval` default export) |
| `evals/evals.config.ts` | Required run-wide defaults (`defineEvalConfig`) |

Eval identity is path-derived: `evals/weather/brooklyn-forecast.eval.ts` becomes id `weather/brooklyn-forecast`. Array exports in one file receive zero-padded index suffixes (`weather/0000`). Eval discovery runs independently of `eve build` and does not produce `.eve/` artifacts.

## Compile pipeline and `.eve/` artifacts

`eve build` (and dev-server startup) runs discovery, then writes compiler-owned artifacts. The pipeline is: resolve project layout → walk `agent/` → emit discovery manifest → compile normalized manifest → materialize workspace resources.

```mermaid
flowchart LR
  subgraph authored [Authored surface]
    A["agent/**"]
  end
  subgraph discovery [".eve/discovery/"]
    D1["agent-discovery-manifest.json"]
    D2["diagnostics.json"]
  end
  subgraph compile [".eve/compile/"]
    C1["compiled-agent-manifest.json"]
    C2["module-map.mjs"]
    C3["compile-metadata.json"]
    C4["workspace-resources/"]
    C5["channel-instrumentation-types.d.ts"]
  end
  A --> D1
  A --> D2
  D1 --> C1
  C1 --> C2
  C1 --> C4
```

### Discovery artifacts (`.eve/discovery/`)

| File | Contents |
| --- | --- |
| `agent-discovery-manifest.json` | Raw discovery manifest: source refs, logical paths, subagent graph before module import |
| `diagnostics.json` | Structured discovery diagnostics with error and warning counts |

### Compile artifacts (`.eve/compile/`)

| File | Contents |
| --- | --- |
| `compiled-agent-manifest.json` | Versioned runtime manifest (`eve-agent-compiled-manifest`, schema version 29): normalized config, tools, connections, skills, channels, schedules, hooks, sandbox metadata, flattened subagent nodes and edges, `remoteAgents`, `workspaceResourceRoot` |
| `module-map.mjs` | Compiled module map for loading authored exports at runtime |
| `compile-metadata.json` | Generator version, artifact SHA-256 digests, `status` (`ready` or `failed`), discovery summary |
| `workspace-resources/` | Per-node materialized skill packages and sandbox workspace trees |
| `channel-instrumentation-types.d.ts` | Generated channel instrumentation types |

<Warning>
When discovery reports errors, `compile-metadata.json` sets `status` to `failed` and `eve build` throws. Warnings alone still produce artifacts with `status: ready`.
</Warning>

The compiled manifest flattens the subagent tree: each local subagent becomes a `CompiledSubagentNode` with its own `CompiledAgentNodeManifest`, connected to its parent via `subagentEdges`. Root agent node id is `__root__`; nested ids compose from parent and source id.

## Verify discovery

<Steps>
<Step title="Run eve info">

```sh
eve info
```

Inspect layout, compile status, diagnostics summary, and artifact paths.

</Step>
<Step title="Inspect diagnostics when counts are non-zero">

Open `.eve/discovery/diagnostics.json` for structured error and warning entries with source paths and diagnostic codes.

</Step>
<Step title="Confirm compile metadata">

Check `.eve/compile/compile-metadata.json` for `status: ready` before deploying or starting production.

</Step>
</Steps>

`eve info --json` emits the same artifact paths and discovered tool/skill lists that the runtime serves from the compiled manifest.

## Flat layout alternative

When the app root is also the agent root:

:::files
my-agent/
├── package.json
├── agent.ts
├── instructions.md
├── tools/
└── skills/
:::

Prefer the nested layout for new projects. It keeps package scaffolding, evals, and the authored agent surface in separate trees.

## Related pages

<CardGroup>
<Card title="Overview" href="/overview">
Filesystem-first agent model, channel/harness/runtime split, and the shortest path from init to a running session.
</Card>
<Card title="Quickstart" href="/quickstart">
Scaffold with `eve init`, verify with `eve info`, iterate with `eve dev`.
</Card>
<Card title="Subagents and schedules" href="/subagents-and-schedules">
Local subagents, remote agents, cron schedules, and delegation boundaries.
</Card>
<Card title="Instrumentation and evals" href="/instrumentation-and-evals">
`defineInstrumentation`, `defineEval`, `eve eval`, and reporters.
</Card>
<Card title="CLI reference" href="/cli-reference">
All `eve` commands, flags, `.eve/` artifact paths, and the edit-info-dev-build-start loop.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Discovery diagnostics, common failure modes, and runtime error codes.
</Card>
</CardGroup>

---

## 05. Execution model and durability

> Session/turn/step nesting, durable workflow checkpoints, crash resume semantics, parked work (HITL, OAuth, subagents), and message delivery constraints on continuationToken.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/05-execution-model-and-durability.md
- Generated: 2026-06-16T19:24:05.887Z

### Source Files

- `docs/concepts/execution-model-and-durability.md`
- `packages/eve/src/execution/workflow-runtime.ts`
- `packages/eve/src/execution/durable-session-migrations/chain.ts`
- `packages/eve/src/execution/next-driver-action.ts`
- `packages/eve/src/execution/dispatch-runtime-actions-step.ts`

---
title: "Execution model and durability"
description: "Session/turn/step nesting, durable workflow checkpoints, crash resume semantics, parked work (HITL, OAuth, subagents), and message delivery constraints on continuationToken."
---

Eve runs every agent session as a durable Workflow SDK workflow: a long-lived driver (`workflowEntry`) owns the event stream and dispatches each turn as a short-lived child workflow (`turnWorkflow`). Session program memory checkpoints at `"use step"` boundaries through versioned `DurableSessionState` snapshots; channels resume parked sessions with `continuationToken` via `resumeHook`, not a durable message queue.

## Session, turn, and step nesting

Work nests in three levels. Each level maps to a distinct runtime scope and stream vocabulary.

| Level | Scope | Runtime owner | Typical stream events |
| --- | --- | --- | --- |
| **Session** | Whole durable conversation or task | `workflowEntry` driver workflow | `session.started`, `session.waiting`, `session.completed`, `session.failed` |
| **Turn** | One inbound delivery and all work it triggers until the agent responds | `turnWorkflow` child workflow | `turn.started`, `turn.completed`, `turn.failed` |
| **Step** | One model call and the tool calls it makes | `turnStep` (`"use step"` boundary) | `step.started`, `step.completed`, `step.failed` |

Within a turn, the harness tool loop may run multiple steps. `StepNext` tells the runtime what happens after each step:

- A `StepFn` reference — continue immediately (tool-loop iteration).
- `null` — park and wait for the next delivery.
- `StepDone` — terminal turn output.

Conversation history inside a session is append-only. Turns land in order; tool calls and results inside a turn keep their order.

```mermaid
stateDiagram-v2
  [*] --> ActiveTurn: deliver / initial message
  ActiveTurn --> ActiveStep: turnStep (use step)
  ActiveStep --> ActiveStep: tool loop continues
  ActiveStep --> Parked: stepResult.next === null
  ActiveStep --> Done: stepResult.next.done
  Parked --> ActiveTurn: resumeHook on continuationToken
  Done --> Parked: mode === conversation
  Done --> [*]: mode === task
```

## Two-workflow architecture

Eve splits durability across a driver and per-turn children.

```mermaid
flowchart TB
  subgraph driver ["Driver — workflowEntry (pinned deployment)"]
    DL[runDriverLoop]
    PH[park hook on continuationToken]
    AH[auth hook on sessionId:auth]
    DL --> PH
    DL --> AH
  end

  subgraph turn ["Turn — turnWorkflow (latest deployment on Vercel production)"]
    TS[turnStep]
    HL[tool-loop harness]
    TS --> HL
  end

  subgraph persist ["Persistence boundary"]
    DSS[DurableSessionState snapshot]
  end

  DL -->|dispatchTurnStep| turn
  turn -->|NextDriverAction via completion hook| DL
  TS --> DSS
```

<ParamField body="workflowEntry" type="workflow">
Long-lived entrypoint. Creates the session, runs the driver loop, owns the NDJSON event stream (`getWritable`), and parks on `continuationToken`. Pinned to the deployment that called `start()`.
</ParamField>

<ParamField body="turnWorkflow" type="workflow">
Short-lived child that owns one turn. Runs `turnStep` in a loop until the turn resolves to a `NextDriverAction`. On Vercel production, routes to `deploymentId: "latest"` so turn code can roll forward independently of the pinned driver.
</ParamField>

<ParamField body="turnStep" type="step">
Atomic harness step inside `"use step"`. Reads the durable session, runs one tool-loop iteration, writes an updated `DurableSessionState` snapshot into the step result.
</ParamField>

<Note>
Workflow primitives (`start()`, `resumeHook()`, `createHook()`) are implementation details of Eve's runtime layer. Channels, tools, and hooks never call them directly.
</Note>

## Durable checkpoints and wire migrations

Session-mutating steps return `DurableSessionState` as the atomic persistence boundary. The state carries:

<ResponseField name="sessionId" type="string">
Workflow run id for the session.
</ResponseField>

<ResponseField name="continuationToken" type="string">
Hook token clients use to resume the parked session. May differ from `sessionId` when a channel re-keys mid-session.
</ResponseField>

<ResponseField name="snapshot" type="DurableSessionSnapshot">
Embedded session projection (history, sandbox state, `session.state`, compaction counters). Optional for legacy stream-backed sessions that fall back to the `eve.session` namespace tail.
</ResponseField>

<ResponseField name="hasProxyInputRequests" type="boolean">
Closed-contract short-circuit. When `false`, the driver skips per-delivery proxy-routing steps for descendant HITL.
</ResponseField>

<ResponseField name="emissionState" type="object">
`{ turnId, sequence, stepIndex }` for stamping protocol events without re-reading the full session in the workflow body.
</ResponseField>

The driver workflow can stay pinned while child turn workflows run on a newer deployment. Wire shapes therefore carry a `version` field; `runMigrationChain` walks registered migrations so a value written at any historic version reads at the current one. Shape-breaking changes bump `version` and add a migrator; adding optional fields is forward-compatible.

## Crash resume semantics

Durability is on by default — no configuration required.

<Check>
**Completed steps never re-run.** The Workflow SDK replays recorded step results at boundaries.
</Check>

<Warning>
**Interrupted steps re-run.** A step that was mid-execution when the process crashed, timed out, or redeployed executes again from the start. Gate non-idempotent side effects (charges, emails, external writes) with approval predicates or make them idempotent.
</Warning>

After a recoverable step or turn failure, Eve emits `step.failed` → `turn.failed` → `session.waiting` (conversation mode) so the session parks and accepts a follow-up delivery. Structural failures emit `session.failed` instead; the continuation token is then dead.

## Driver action contract

Each turn resolves to a `NextDriverAction` the driver dispatches on. This is a closed contract — adding a new `kind` is breaking for pinned drivers.

| `kind` | When emitted | Driver behavior |
| --- | --- | --- |
| `done` | Turn produced terminal output | Fire session callback, notify delegated parent, exit driver |
| `park` | Turn waiting on human input, OAuth, or adapter-only delivery | Register park hook, emit `session.waiting`, block until `resumeHook` |
| `dispatch-runtime-actions` | Pending subagent or remote-agent calls | Start child sessions, wait for `runtime-action-result` deliveries |
| `dispatch-code-mode-runtime-actions` | Code-mode runtime action interrupt | Dispatch code-mode children, same wait pattern |

The turn workflow chooses `park` vs `dispatch-runtime-actions` at the park boundary:

- Pending runtime actions (subagent calls) → `dispatch-runtime-actions`
- Pending OAuth (`authorizationNames`) → `park` on the auth hook (`{sessionId}:auth`)
- Pending HITL input batch with `capabilities.requestInput === true` → `park`
- `mode === "conversation"` with no pending work → `park` (between-turn wait)
- `mode === "task"` with no pending work and no parkable state → **throws** (`Task mode cannot wait for follow-up input`)

Task-mode scheduled chains therefore must finish in one invocation; they emit `session.completed` instead of `session.waiting` at turn epilogue.

## Parked work

When work must wait, the driver suspends the workflow and holds no compute until input arrives.

### HITL (tool approval and `ask_question`)

Tool approval gates and `ask_question` register only when `SessionCapabilities.requestInput` is `true`. Channel routes that can reach a human (HTTP, Slack, etc.) set this at session creation; scheduled task routes omit it.

When the harness parks on a pending input batch, the turn emits `turn.completed` then `session.waiting` with `data.wait: "next-user-message"`. Clients deliver `inputResponses` on the next `POST` to the session continue route.

Subagent HITL is proxied through the parent: a child sends `subagent-input-request` on the parent's hook; the parent re-emits `input.requested` and routes `inputResponses` back to the child's `continuationToken` via `routeProxiedDeliverStep`.

### OAuth and connection authorization

When a tool calls `requireAuth` and authorization is pending, the turn parks with `authorizationNames`. The driver waits on a separate auth hook at `{sessionId}:auth`, independent of the park `continuationToken`. OAuth callback URLs embed this token so channel re-keying mid-turn does not invalidate callbacks.

Deliveries carrying `authorizationCallback` in the payload complete pending challenges; `authorization.completed` events emit before the harness resumes.

### Subagents and remote agents

When the `agent` tool (or code-mode equivalent) delegates work, the turn parks with `dispatch-runtime-actions`. `dispatchRuntimeActionsStep` starts each child as its own `workflowEntry` run, records the child's `continuationToken` on the parent session, and emits `subagent.called` on the parent stream. The driver accumulates `runtime-action-result` deliveries before dispatching the next turn.

Each subagent gets its own durable session, sandbox, skills, and state. Nothing crosses the boundary implicitly; the parent receives results through the runtime-action channel.

```mermaid
sequenceDiagram
  participant Client
  participant Driver as workflowEntry
  participant Turn as turnWorkflow
  participant Child as subagent workflowEntry

  Client->>Driver: deliver (continuationToken)
  Driver->>Turn: dispatchTurnStep
  Turn->>Turn: turnStep → park (subagent pending)
  Turn-->>Driver: dispatch-runtime-actions
  Driver->>Child: childRuntime.run
  Driver-->>Client: subagent.called
  Child-->>Driver: runtime-action-result hook
  Driver->>Turn: dispatchTurnStep (results)
  Turn-->>Client: turn.completed, session.waiting
```

## `continuationToken` and message delivery

`continuationToken` is a resume handle for the session's current workflow hook — not a durable FIFO message queue.

<ParamField body="continuationToken" type="string" required>
Token passed to `Runtime.deliver()` and embedded in `RunHandle` after `Runtime.run()`. Defaults to the workflow `runId` when omitted at session creation. Channels may re-key via `ctx.session.setContinuationToken(...)` during the first turn (e.g. Slack thread anchoring).
</ParamField>

<ParamField body="sessionId" type="string" required>
Runtime-owned identifier for stream and inspection APIs. For workflow-backed runs, equals the workflow run id. Differs from `continuationToken` when a channel assigns a custom hook token.
</ParamField>

### Deliver and resume-or-start

`Runtime.deliver()` calls `resumeHook(continuationToken, hookPayload)`. If no hook matches, Eve throws `RuntimeNoActiveSessionError` (`code: "NO_ACTIVE_SESSION"`). Callers using the resume-or-start pattern treat this as the signal to start a fresh session.

The park hook is registered **before** the first turn completes so fast clients cannot POST follow-ups before the hook exists.

### Coalescing and ordering constraints

Eve does not maintain a durable per-session message queue.

| Situation | Behavior |
| --- | --- |
| Session parked (`session.waiting`) | One delivery to `continuationToken` wakes the session and starts the next turn |
| Turn already active | The hook may accept additional deliveries, but the driver drains them only at specific workflow boundaries |
| Multiple deliveries ready at drain time | Eve may coalesce them into one turn (`coalesceDeliveries`); drain is best-effort and timing-dependent |

<Warning>
Do not rely on concurrent sends to the same session behaving like an ordered chat queue. Send one user turn at a time and wait for `session.waiting` before delivering the next message. If your channel receives bursts while the agent is working, buffer per-session in the channel or app layer and deliver after the session parks again.
</Warning>

Re-keying replaces the park hook at a new token. In-flight deliveries to the old token after re-key are silently dropped — channels that re-key must coordinate senders.

Separate sessions run independently; each has its own driver workflow and hook namespace (`WORKFLOW_QUEUE_NAMESPACE=eve`).

## Run modes and terminal events

<ResponseField name="conversation" type="RunMode">
Turn epilogue emits `turn.completed` then `session.waiting`. Session parks between turns indefinitely.
</ResponseField>

<ResponseField name="task" type="RunMode">
Turn epilogue emits `turn.completed` then `session.completed`. Cannot park on empty follow-up input. Used for subagent delegation, schedules, and function-output contracts.
</ResponseField>

## Accessing session data from authored code

Two surfaces expose session data without touching workflow primitives:

- **`ctx.session`** — read current session metadata (id, turn, auth, parent lineage) from tools and hooks.
- **`defineState`** — read/write session-scoped durable state that persists across step boundaries.

For stream consumption, event ordering, and HTTP continue routes, see the sessions and streaming page.

## Related pages

<CardGroup>
  <Card title="Sessions and streaming" href="/sessions-and-streaming">
    `continuationToken` vs `sessionId`, NDJSON events, reconnect with `startIndex`, and subagent child-session attachment.
  </Card>
  <Card title="Tools" href="/tools">
    HITL approval predicates, `requireAuth`, and tool execution inside durable steps.
  </Card>
  <Card title="Subagents and schedules" href="/subagents-and-schedules">
    Local subagents, remote agents, cron schedules, and task-mode execution constraints.
  </Card>
  <Card title="State, hooks, and session context" href="/state-hooks-and-context">
    `defineState` persistence, `defineHook` subscribers, and managed-context APIs.
  </Card>
  <Card title="Connections" href="/connections">
    OAuth callback routes and authorization parking semantics.
  </Card>
</CardGroup>

---

## 06. Sessions and streaming

> continuationToken vs sessionId contracts, POST /eve/v1/session and follow-up routes, NDJSON stream events, reconnect behavior, and subagent child-session attachment.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/06-sessions-and-streaming.md
- Generated: 2026-06-16T19:24:46.601Z

### Source Files

- `docs/concepts/sessions-runs-and-streaming.md`
- `packages/eve/src/protocol/routes.ts`
- `packages/eve/src/client/open-stream.ts`
- `packages/eve/src/client/ndjson.ts`
- `packages/eve/src/client/session.ts`
- `packages/eve/src/internal/nitro/routes/index.ts`

---
title: "Sessions and streaming"
description: "continuationToken vs sessionId contracts, POST /eve/v1/session and follow-up routes, NDJSON stream events, reconnect behavior, and subagent child-session attachment."
---

Eve exposes session lifecycle over stable `/eve/v1` HTTP routes registered by `eveChannel`. A POST acknowledges work immediately and returns handles; a GET on `/stream` replays durable NDJSON events from the workflow-backed session run. The `continuationToken` resumes parked work; `sessionId` keys the event stream.

## Two handles

| Handle | Role | Used for |
| --- | --- | --- |
| `continuationToken` | Resume handle for the session's current workflow hook | POST follow-ups (`message`, `inputResponses`) while the session is parked or active |
| `sessionId` | Stream-and-inspect handle (workflow run id) | `GET /eve/v1/session/:sessionId/stream`, `x-eve-session-id` response header |

<Warning>
Do not interchange these handles. `continuationToken` is owned by the channel and namespaced internally as `<channelName>:<rawToken>` (for example `eve:<uuid>` on the default HTTP channel). `sessionId` is runtime-owned and stable for the durable run.
</Warning>

A session has one active continuation at a time. Channels may re-key the token mid-session (for example Slack anchoring a thread `ts` via `ctx.session.setContinuationToken`). Deliveries to a superseded token after re-key are silently dropped. A stale token with no matching parked hook yields `RuntimeNoActiveSessionError`; the HTTP `send` path may fall back to starting a new session for plain messages, but rejects `inputResponses` when the target session cannot be found.

## Session routes

The default `eveChannel` registers three routes under `/eve/v1`. All run the channel's `auth` chain via `routeAuth` before dispatch.

| Method | Path | Status | Purpose |
| --- | --- | --- | --- |
| `POST` | `/eve/v1/session` | `202` | Create a session and start the first turn |
| `POST` | `/eve/v1/session/:sessionId` | `200` | Continue a session with `continuationToken` |
| `GET` | `/eve/v1/session/:sessionId/stream` | `200` | Stream NDJSON lifecycle events |

```mermaid
sequenceDiagram
  participant Client
  participant EveChannel as eveChannel routes
  participant Runtime as Workflow runtime
  participant Stream as Durable event stream

  Client->>EveChannel: POST /eve/v1/session { message }
  EveChannel->>Runtime: runtime.run (new session)
  Runtime-->>EveChannel: sessionId, continuationToken
  EveChannel-->>Client: 202 JSON + x-eve-session-id

  Client->>EveChannel: GET /eve/v1/session/:sessionId/stream
  EveChannel->>Stream: getEventStream(sessionId)
  Stream-->>Client: application/x-ndjson events

  Note over Client,Runtime: Turn completes → session.waiting
  Client->>EveChannel: POST /eve/v1/session/:sessionId { continuationToken, message }
  EveChannel->>Runtime: runtime.deliver
  Runtime-->>Client: 200 { sessionId }
```

:::endpoint POST /eve/v1/session
Create a session and enqueue the first user turn. Returns immediately; progress arrives on the stream route.

<ParamField body="message" type="string | UserContent" required>
Plain text or an AI SDK `UserContent` array (`text` and `file` parts). Required on create.
</ParamField>

<ParamField body="clientContext" type="string | string[] | object">
One-turn client/page context. Strings become user-role context messages prefixed with `Client context:\n`. Not persisted to durable history.
</ParamField>

<ParamField body="outputSchema" type="object">
JSON Schema the turn result must satisfy before the turn terminates.
</ParamField>

<ParamField body="mode" type="'conversation' | 'task'">
Run mode. `conversation` enables recoverable input requests; `task` fails when input would be required.
</ParamField>

<ParamField body="callback" type="object">
Optional terminal callback metadata. The runtime posts once when the session completes or fails.
</ParamField>

<ResponseField name="continuationToken" type="string">
Channel-local resume token (raw form, without the `eve:` prefix in the JSON body).
</ResponseField>

<ResponseField name="sessionId" type="string">
Durable session id for streaming.
</ResponseField>

<ResponseField name="x-eve-session-id" type="header">
Same value as `sessionId`. Present on create and continue responses.
</ResponseField>

<RequestExample>

```bash
curl -X POST http://127.0.0.1:3000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Summarize the latest forecast."}'
```

</RequestExample>

<ResponseExample>

```json
{
  "continuationToken": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "ok": true,
  "sessionId": "wrun_abc123"
}
```

</ResponseExample>
:::

:::endpoint POST /eve/v1/session/:sessionId
Deliver a follow-up to a parked or active session. Requires a valid `continuationToken` from the current session state.

<ParamField path="sessionId" type="string" required>
Existing session id. Must resolve via `getSession`; otherwise `404`.
</ParamField>

<ParamField body="continuationToken" type="string" required>
Current resume token. Missing or empty → `400`.
</ParamField>

<ParamField body="message" type="string | UserContent">
Follow-up user message. At least one of `message` or `inputResponses` is required.
</ParamField>

<ParamField body="inputResponses" type="InputResponse[]">
HITL responses resolving pending `input.requested` batches (tool approvals, `ask_question` answers).
</ParamField>

<ParamField body="clientContext" type="string | string[] | object">
Same semantics as create; applies to this delivery only.
</ParamField>

<ParamField body="outputSchema" type="object">
Per-turn structured output schema override.
</ParamField>

<RequestExample>

```bash
curl -X POST http://127.0.0.1:3000/eve/v1/session/wrun_abc123 \
  -H 'content-type: application/json' \
  -d '{"continuationToken":"f47ac10b-58cc-4372-a567-0e02b2c3d479","message":"Now send the short version."}'
```

</RequestExample>

<ResponseExample>

```json
{
  "ok": true,
  "sessionId": "wrun_abc123"
}
```

</ResponseExample>

<Note>
Continue responses do not rotate `continuationToken` in the JSON body. Keep the token from create (or from channel re-keying via stream-side state) until the channel issues a new one.
</Note>
:::

:::endpoint GET /eve/v1/session/:sessionId/stream
Replay or tail the durable NDJSON event stream for one session.

<ParamField path="sessionId" type="string" required>
Session to stream. Unknown id → `404`.
</ParamField>

<ParamField query="startIndex" type="integer">
Zero-based event index to start from. Omit to replay from the beginning; set to the number of events already consumed to reconnect without duplication. Negative or non-integer → `400`.
</ParamField>

<ResponseField name="content-type" type="header">
`application/x-ndjson; charset=utf-8`
</ResponseField>

<ResponseField name="x-eve-stream-format" type="header">
`ndjson`
</ResponseField>

<ResponseField name="x-eve-stream-version" type="header">
`15` (current stream schema version)
</ResponseField>

<ResponseField name="x-eve-session-id" type="header">
Echoes the requested `sessionId`.
</ResponseField>

<RequestExample>

```bash
curl "http://127.0.0.1:3000/eve/v1/session/wrun_abc123/stream?startIndex=42"
```

</RequestExample>
:::

## NDJSON event stream

Each line is one JSON object: a `HandleMessageStreamEvent` optionally stamped with `meta.at` (ISO timestamp) at write time. The stream is durable — events are recorded before a step completes, so the full history is replayable.

### Turn and session boundaries

Clients treat these as turn/session boundaries:

| Event | Meaning |
| --- | --- |
| `session.waiting` | Session parked; safe to send the next follow-up with the current `continuationToken` |
| `session.completed` | Terminal success |
| `session.failed` | Terminal failure; carries `{ code, message, details? }` |

`ClientSession` and `openStreamIterable` stop a per-turn consumer at the first `session.completed`, `session.failed`, or `session.waiting` event (`isCurrentTurnBoundaryEvent`).

### Lifecycle events

| Event | Meaning |
| --- | --- |
| `session.started` | Durable session created; child runs include `data.invocation` (`kind: "subagent"`, parent ids) |
| `turn.started` | New turn; `data.turnId`, `data.sequence` |
| `message.received` | Inbound user message accepted |
| `step.started` | Model step began |
| `actions.requested` | Tool call batch requested (`data.actions`) |
| `action.result` | Tool result projected (`data.status`: `completed` \| `failed`) |
| `input.requested` | HITL pause; `data.requests` |
| `step.completed` | Model step finished; `data.finishReason`, optional `data.usage` |
| `step.failed` | Step failure; `{ code, message, details? }` |
| `turn.completed` | Turn succeeded |
| `turn.failed` | Turn failure; `{ code, message, details? }` |
| `result.completed` | Structured output for schema turns; `data.result` |
| `compaction.requested` / `compaction.completed` | Context compaction checkpoint |
| `authorization.required` / `authorization.completed` | Connection OAuth challenge and outcome |

### Streaming text and reasoning

| Event | Meaning |
| --- | --- |
| `message.appended` | Assistant text delta; `messageDelta` + cumulative `messageSoFar` |
| `message.completed` | Finalized assistant text block; `data.finishReason` |
| `reasoning.appended` | Reasoning delta; `reasoningDelta` + `reasoningSoFar` |
| `reasoning.completed` | Finalized reasoning block |

`message.completed` may fire multiple times per turn. `finishReason: "tool-calls"` marks interim narration before a tool call; other values mark a terminal assistant message for that step. Pair deltas (`*.appended`) with finalized blocks (`*.completed`) when building UIs.

### Subagent events on the parent stream

| Event | Meaning |
| --- | --- |
| `subagent.called` | Workflow subagent delegated; includes `data.childSessionId` for child stream attachment |
| `subagent.started` | Inline subagent execution began |
| `subagent.event` | Wraps one child stream event under `data.event` (inline path) |
| `subagent.completed` | Inline subagent finished; `data.output` |

## Message delivery constraints

`continuationToken` names the session's workflow hook, not a durable FIFO message queue. When the session is waiting, one delivery wakes the next turn. Concurrent sends while a turn is active are best-effort at workflow boundaries and may be folded together.

<Tip>
For deterministic ordering, send one user turn at a time and wait for `session.waiting` before the next POST to the same session. Burst-prone channels should queue locally and deliver after each park.
</Tip>

HITL deliveries (`inputResponses` without a new `message`) retry up to 10 times on `500` responses whose body matches `target session was not found` — covering the race where a client posts before the park hook is registered.

## Reconnect and rewind

**Server replay:** Pass `?startIndex=<count>` where `count` is the number of events already consumed. The runtime calls `getRun(sessionId).getReadable({ startIndex })` and drops earlier events.

**Client reconnection:** `eve/client` tracks `streamIndex` in `SessionState`. `openStreamIterable` reconnects on transient socket disconnects (matching `isStreamDisconnectError`), incrementing `startIndex` as events arrive, up to `maxReconnectAttempts` (default `3`). Clean EOF and caller aborts do not reconnect.

**Stream open retries:** `openStreamBody` retries up to 12 times (250 ms delay) on status `404`, `409`, `425`, `500`, `502`, `503`, `504` while a just-created session propagates to the stream route.

After a turn, `advanceSession` updates `continuationToken` and `streamIndex` on `session.waiting`; on `session.completed` or `session.failed` it resets client state unless `preserveCompletedSessions: true`.

## Subagent child-session attachment

Workflow subagents run as independent durable child sessions. The parent stream emits `subagent.called` with:

- `data.childSessionId` — attach here
- `data.callId`, `data.toolName`, `data.name`
- `data.sessionId`, `data.turnId`, `data.workflowId`
- `data.remote.url` when delegating to a `defineRemoteAgent` target

Child progress is **not** mirrored on the parent stream (except the inline `subagent.event` path). Open a second stream:

```bash
curl "http://127.0.0.1:3000/eve/v1/session/<childSessionId>/stream"
```

Child `session.started` events carry `data.invocation` with `kind: "subagent"` and parent lineage (`parentSessionId`, `parentTurnId`, `parentCallId`, `name`).

When a child needs HITL, `SUBAGENT_ADAPTER` forwards `input.requested` batches to the parent continuation token via `resumeHook`. The parent channel renders prompts; responses route back down using the child's continuation token recorded on the parent session.

## TypeScript client

`eve/client` wraps the HTTP contract:

```typescript
import { Client } from "eve/client";

const client = new Client({ host: "http://127.0.0.1:3000" });
const session = client.session();

const response = await session.send("Summarize the latest forecast.");

for await (const event of response) {
  if (event.type === "subagent.called") {
    const child = client.session({ sessionId: event.data.childSessionId });
    for await (const childEvent of child.stream()) {
      // render child progress
    }
  }
}

const result = await response.result();
// result.status: "waiting" | "completed" | "failed"
// result.message: terminal assistant text
// result.inputRequests: pending HITL from this turn
```

`ClientSession.send` picks `POST /eve/v1/session` or `POST /eve/v1/session/:sessionId` from stored `sessionId`. Serialize `session.state` (`continuationToken`, `sessionId`, `streamIndex`) to resume later.

## Stream event dispatch order

Inside the harness step loop, each emitted event runs:

1. Channel adapter handler (`adapter[event.type]`) — side effects only; event is not transformed
2. Adapter state persisted to context
3. Event written to the durable stream (with `meta.at`)
4. Authored hooks (`dispatchStreamEventHooks`)
5. Dynamic tool, skill, and instruction resolvers

Channel metadata projected from adapter state is current before hooks and resolvers read `ctx.channel`.

## Related pages

<CardGroup>
<Card title="Execution model and durability" href="/execution-model">
Session/turn/step nesting, parked work, and continuationToken delivery constraints.
</Card>
<Card title="HTTP API reference" href="/http-api">
Full route inventory, error shapes, and NDJSON vocabulary.
</Card>
<Card title="Client integration" href="/client-integration">
`Client`, `ClientSession`, streaming reducers, and framework hooks.
</Card>
<Card title="Subagents and schedules" href="/subagents-and-schedules">
Local subagents, remote agents, and delegation boundaries.
</Card>
</CardGroup>

---

## 07. Default harness

> Built-in agent loop, shipped tools (bash, file ops, web_fetch, todo, ask_question, agent, load_skill, connection_search), compaction defaults, and override/disable patterns.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/07-default-harness.md
- Generated: 2026-06-16T19:25:23.036Z

### Source Files

- `docs/concepts/default-harness.md`
- `packages/eve/src/public/tools/index.ts`
- `packages/eve/src/compiler/normalize-agent-config.ts`
- `packages/eve/src/execution/sandbox/bash-tool.ts`
- `packages/eve/src/execution/sandbox/read-file-tool.ts`
- `docs/agent-config.md`

---
title: "Default harness"
description: "Built-in agent loop, shipped tools (bash, file ops, web_fetch, todo, ask_question, agent, load_skill, connection_search), compaction defaults, and override/disable patterns."
---

Every Eve agent runs on the framework-owned default harness: a durable tool-loop step (`createToolLoopHarness` in `packages/eve/src/harness/tool-loop.ts`) backed by the AI SDK `ToolLoopAgent`, wired per runtime node in `createExecutionNodeStep`. The harness owns turn execution—model calls, tool dispatch, compaction, HITL parking, and stream emission—while authored files under `agent/` supply instructions, tools, connections, and configuration that merge into the resolved toolset at graph build time.

```mermaid
flowchart TB
  subgraph authored ["Authored agent/"]
    instructions["instructions.md/ts"]
    tools["tools/*.ts"]
    connections["connections/*.ts"]
    skills["skills/"]
    agentTs["agent.ts"]
  end

  subgraph compile ["Compile + resolve"]
    graph["resolve-agent-graph"]
    registry["toolRegistry + dynamicToolResolvers"]
  end

  subgraph harness ["Default harness"]
    step["createToolLoopHarness"]
    loop["ToolLoopAgent loop"]
    compact["compaction + state preservation"]
  end

  subgraph surfaces ["Execution surfaces"]
    sandbox["Sandbox proxy: bash, read/write, glob, grep"]
    runtime["App runtime: web_fetch, todo, load_skill, agent"]
    provider["Provider: web_search"]
    dynamic["Dynamic: connection__search + discovered tools"]
  end

  authored --> graph --> registry --> step
  step --> loop
  loop --> compact
  loop --> surfaces
  agentTs --> graph
```

<Info>
The harness is separate from channels (ingress) and the durable workflow runtime (checkpoint/resume). Turn nesting, parked work, and continuation semantics live on the execution-model page; this page covers what the loop ships and how to reshape it.
</Info>

## Agent loop

Each resolved runtime node gets one harness step function. `createExecutionNodeStep` builds a unified `HarnessToolMap` from the node's tool registry and subagent registry, injects the built-in `agent` delegation tool when no tool with that name already exists, and passes the map into `createToolLoopHarness` along with compaction hooks, model resolution, and optional code-mode / Workflow surfaces.

Per step, the harness:

1. Assembles the effective toolset (`buildToolSetWithProviderTools`), including dynamic tools resolved at `step.started` and provider-managed replacements for `web_search`.
2. Runs the model through `ToolLoopAgent`, executing tool calls until the turn completes, parks for HITL/OAuth/subagent input, or hits a terminal error.
3. Evaluates compaction when estimated input tokens exceed the session threshold; on compaction, calls `preserveFrameworkStateOnCompaction` to reset read-before-write stamps and re-inject the active todo list.
4. Emits NDJSON stream events (`step.started`, tool results, `compaction.requested`, `compaction.completed`, `input.requested`, and terminal events).

Tools without a local `execute` function (notably `ask_question`) are client-side: the model can call them, but the runtime parks until the channel delivers user input.

## Compaction defaults

Compaction is on by default for every session. Configuration is authored in `agent/agent.ts` via `defineAgent({ compaction: { ... } })` and compiled into the agent manifest.

| Setting | Default | Behavior |
| --- | --- | --- |
| `compaction.thresholdPercent` | `0.9` | Fraction of the primary model context window that triggers summarization |
| Recent window | `10` messages | Harness keeps the tail of history verbatim; older messages are summarized |
| Compaction summary model | Active turn model | Override with `compaction.model` to use a different model for summaries only |
| Fallback threshold | `100_000` tokens | Used when context-window metadata is unavailable at session creation |

When compaction runs, the harness calls a dedicated summarizer (`compactMessages`) with a fixed system prompt, then appends the summary as a compact checkpoint message. Framework-owned state is preserved automatically—there is no per-tool compaction hook:

- **Read-before-write tracking** is cleared so a subsequent `write_file` must re-read any file whose read evidence was summarized away.
- **Todo list** is re-injected as a user message when non-empty items exist in durable `TodoStateKey`.

Tune compaction in `agent.ts`:

```ts title="agent/agent.ts"
import { defineAgent } from "eve";

export default defineAgent({
  model: "anthropic/claude-opus-4.8",
  compaction: {
    thresholdPercent: 0.75,
    // model: "anthropic/claude-sonnet-4.6", // optional summary-only model
  },
});
```

Stream consumers see `compaction.requested` before the summary call and `compaction.completed` after the checkpoint message is appended.

## Built-in tools

These tools ship with every agent without imports. At graph resolution, framework defaults merge with authored tools: a file at `agent/tools/<slug>.ts` with the same slug replaces the built-in; a `disableTool()` sentinel removes it. Discovery (`eve info`, `eve build`) surfaces descriptors but never executes tools.

The harness shows descriptors to the model first and executes only what the model calls.

| Tool | Purpose | Where it runs |
| --- | --- | --- |
| `bash` | Run a shell command in the workspace | Sandbox (proxied via `executeBashOnSandbox`) |
| `read_file` | Read a text file with line-numbered output; records read stamps | Sandbox FS |
| `write_file` | Write a complete file; enforces read-before-write and stale-read detection | Sandbox FS |
| `glob` | Find files by glob pattern | Sandbox FS |
| `grep` | Search file contents by regex | Sandbox FS |
| `web_fetch` | Fetch a URL (markdown/text/html) | App runtime |
| `web_search` | Search the web via the model provider | Provider (no local `execute`; injected per step) |
| `todo` | Maintain a durable per-session todo list | App runtime (`TodoStateKey`) |
| `ask_question` | Ask the user a clarifying question mid-turn; parks until answered | App runtime (client-side, no `execute`) |
| `agent` | Delegate a subtask to a copy of the current agent | App runtime (subagent-call runtime action) |
| `load_skill` | Pull on-demand skill instructions into the current turn | App runtime (reads `SKILL.md` from sandbox) |
| `connection__search` | Discover tools across declared connections | App runtime (dynamic resolver, connections only) |

### Sandbox and file tools

Shell and filesystem tools (`bash`, `read_file`, `write_file`, `glob`, `grep`) are defined in the app runtime but proxy work into the agent's single sandbox session. `bash` tail-truncates stdout/stderr to shared output limits. `read_file` defaults to `offset: 1`, `limit: 2000` lines, rejects binary files (NUL bytes), and persists a full-file stamp for `write_file` stale-read detection.

### `web_search`

The framework registers `web_search` with a throwing local stub. At step time, `buildToolSetWithProviderTools` replaces it with the real provider tool when the active model supports a known backend (Anthropic, OpenAI, Google, or AI Gateway/Perplexity). Override with `defineTool()` in `agent/tools/web_search.ts` to supply your own executor—the override's `execute` prevents provider injection.

### `ask_question`

`ask_question` has no `execute` method. The harness exposes it only when `SessionCapabilities.requestInput` is `true` (interactive sessions with HITL input). Scheduled task roots and subagent chains without that capability never see the tool. Input schema: `{ prompt, options?, allowFreeform? }`; the turn parks until the channel delivers an answer.

### `agent` (self-delegation)

Unless a subagent tool already occupies the name, the harness injects an `agent` tool that launches a copy of the current agent on a focused subtask. The child inherits the same tools, connections, and instructions but starts with fresh conversation history and fresh state. For self-delegation (`subagentName: "agent"`), the child shares the parent's sandbox filesystem—writes remain visible to the parent.

Input schema (all subagent tools):

```json
{
  "type": "object",
  "properties": {
    "message": { "type": "string" },
    "outputSchema": { "type": "object" }
  },
  "required": ["message"]
}
```

### `load_skill`

`load_skill` is always registered as a framework tool. The **Available skills** system-prompt block—which lists skill names, descriptions, and tells the model when to call `load_skill`—is injected only when the agent declares skills under `agent/skills/`. Without declared skills, the tool has no listed targets in context. Loading adds instructions to the turn; it does not add new execution surfaces.

`load_skill` is never sandboxed, even when `experimental.codeMode` or Workflow is enabled.

### `connection__search`

When the agent declares connections, graph resolution appends a framework dynamic tool resolver (`slug: "connection"`). At each `step.started`, the resolver exposes:

- `connection__search` — keyword search across connections (`keywords`, optional `connection`, optional `limit`)
- `connection__<connection>__<tool>` — directly callable tools discovered from search results

Discovered tools persist in durable context (`ConnectionSearchResultsKey`) so they remain available in code-mode runs where results are wrapped inside the `code_mode` tool. The connections system-prompt section instructs the model to search before calling qualified tools.

<Warning>
The model-facing search tool is `connection__search` (double-underscore namespace), not `connection_search`. Qualified connection tools use the pattern `connection__<connection>__<tool>`.
</Warning>

## Override a default

Author a tool at the same slug under `agent/tools/` and it replaces the built-in of that name. Spread defaults from `eve/tools/defaults` to keep description, schema, and framework state wiring:

```ts title="agent/tools/write_file.ts"
import { defineTool } from "eve/tools";
import { writeFile } from "eve/tools/defaults";

export default defineTool({
  ...writeFile,
  async execute(input, ctx) {
    console.log("[write_file]", input.path);
    return writeFile.execute(input, ctx);
  },
});
```

Exportable defaults: `bash`, `readFile`, `writeFile`, `glob`, `grep`, `webFetch`, `webSearch`, `todo`, `loadSkill`.

Specialized factories (`defineBashTool`, `defineReadFileTool`, `defineWriteFileTool`, `defineGlobTool`, `defineGrepTool`) share the same schemas and executors as the framework definitions.

<Note>
Spreading `todo` keeps the framework's durable `TodoStateKey` behavior. A fresh `defineTool({ name: "todo", ... })` without the spread owns its own state semantics and loses compaction re-injection wiring.
</Note>

## Disable a default

Export `disableTool()` as the default export from `agent/tools/<slug>.ts`. The filename slug selects which framework tool to remove:

```ts title="agent/tools/bash.ts"
import { disableTool } from "eve/tools";

export default disableTool();
```

If the slug matches no known framework tool, graph resolution fails at build time with an explicit error listing valid framework tool names—typos surface early instead of silently doing nothing.

## When to override, disable, or author

| Goal | Action |
| --- | --- |
| Same capability, different behavior (logging, guards, backend) | **Override** — same slug, spread default from `eve/tools/defaults` |
| Remove a capability entirely (lock down shell or web fetch) | **Disable** — `disableTool()` sentinel |
| New capability the harness does not ship | **Author** — new slug under `agent/tools/` |

## Experimental Workflow tool

The `Workflow` tool is shipped but off by default. Enable it by re-exporting the opt-in marker from `agent/tools/workflow.ts`:

```ts title="agent/tools/workflow.ts"
export { ExperimentalWorkflow as default } from "eve/tools";
```

With Workflow enabled, the model gets a QuickJS sandbox (`Workflow` surface) whose callable operations are the agent's subagents and remote agents—model-authored JavaScript orchestrating delegation as one durable step. This composes with `experimental.codeMode` (`code_mode` surface), which sandboxes executable host tools plus subagents.

## Code mode interaction

When `experimental.codeMode` is `true` in `agent.ts`, executable tools route through the `code_mode` sandbox wrapper unless listed in `NEVER_SANDBOXED_TOOL_NAMES` (`load_skill`). Provider-managed tools without local executors are never sandboxed. See agent-configuration for the `codeMode` flag.

## Verification

After changing tools or compaction settings:

<Steps>
<Step title="Rebuild and inspect discovery">

```sh
pnpm exec eve build
pnpm exec eve info
```

Confirm the tool list reflects overrides/disables and that connection or skill surfaces match your authored files.

</Step>
<Step title="Exercise a dev session">

```sh
pnpm exec eve dev
```

Create or continue a session against `/eve/v1/session` and confirm built-in tools execute in the expected surface (sandbox vs runtime vs provider).

</Step>
</Steps>

If graph resolution fails on `disableTool()`, check the error for the list of valid framework tool slugs and rename the file to match.

## Related pages

<CardGroup>
<Card title="Execution model and durability" href="/execution-model">
Session/turn/step nesting, durable checkpoints, crash resume, and parked work semantics.
</Card>
<Card title="Tools" href="/tools">
`defineTool`, HITL approval, `toModelOutput`, and dynamic tool resolution beyond built-ins.
</Card>
<Card title="Sandbox" href="/sandbox">
Sandbox backends, workspace seeding, and how shell/file tools proxy execution.
</Card>
<Card title="Configure agent.ts" href="/agent-configuration">
`defineAgent` fields including `compaction`, `modelOptions`, and `experimental.codeMode`.
</Card>
<Card title="Context control" href="/context-control">
Instructions vs skills, workspace visibility, and subagent context isolation.
</Card>
</CardGroup>

---

## 08. Context control

> Always-on instructions vs on-demand skills, workspace visibility through sandbox tools, dynamic capabilities (defineDynamic), and subagent context isolation.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/08-context-control.md
- Generated: 2026-06-16T19:25:07.388Z

### Source Files

- `docs/concepts/context-control.md`
- `packages/eve/src/compiler/normalize-instructions.ts`
- `packages/eve/src/compiler/normalize-skill.ts`
- `packages/eve/src/execution/skills/instructions.ts`
- `docs/guides/dynamic-capabilities.md`
- `packages/eve/src/compiler/workspace-resources.ts`

---
title: "Context control"
description: "Always-on instructions vs on-demand skills, workspace visibility through sandbox tools, dynamic capabilities (defineDynamic), and subagent context isolation."
---

Eve assembles model context in layers: `composeRuntimeBasePrompt` builds the static system prompt from authored instructions, a shallow workspace overview, connection summaries, and skill routing hints; the harness tool-loop appends runtime-resolved dynamic instructions and skill announcements before each model call; `load_skill` injects full skill bodies as tool results rather than expanding the system prompt.

## Context layers

```text
System prompt (every model call)
├── Static instructions (build-time composed)
├── Workspace overview (root entries only)
├── Available skills (name + description, not body)
├── Connections section (when declared)
├── Tool execution guidance (when tools exist)
├── Dynamic instructions (session.started / turn.started)
└── Dynamic skill announcement (when manifest changes)

Turn-local context
├── load_skill tool results (full SKILL.md body)
├── Tool call / result history
└── Compaction summaries (when threshold crossed)

Sandbox inspection (/workspace)
├── bash, read_file, write_file, glob, grep
└── Packaged skill siblings (references/, scripts/, assets/)
```

<Note>
Active skill markdown never appears in the static system prompt. The model receives skill bodies from `load_skill` tool results, which keeps the system prompt stable across a session and preserves prompt-cache hits.
</Note>

## Always-on instructions

Instructions are the permanent system prompt. Eve prepends them to every model call in a session.

| Source | When resolved | Runtime behavior |
| --- | --- | --- |
| `agent/instructions.md` | Build time | Markdown captured into compiled manifest |
| `agent/instructions.ts` with `defineInstructions` | Build time (once) | Module runs once at compile; resulting markdown is frozen in manifest |
| `agent/instructions/` directory | Build time | Non-recursive; entries compose in filename order after any root `instructions.md` |
| `agent/instructions/*.ts` with `defineDynamic` | Runtime | Resolver runs on `session.started` and `turn.started` |

Static module-backed instructions execute once at build time. There is no per-session re-evaluation for `defineInstructions` exports.

```ts title="agent/instructions.ts"
import { defineInstructions } from "eve/instructions";
import { buildInstructionsPrompt } from "./lib/prompts.js";

export default defineInstructions({
  markdown: buildInstructionsPrompt(),
});
```

Keep instructions short and stable: identity, tone, standing rules. Long or situational procedures belong in skills.

Dynamic instruction resolvers return `defineInstructions({ markdown })` and store output in durable session or turn keys. The tool-loop calls `buildDynamicInstructionMessages` before each model call to flatten session-scoped entries first, then turn-scoped entries.

## On-demand skills

Skills stay out of the always-on prompt. Eve advertises each skill's name and description in an **Available skills** section and registers the framework-owned `load_skill` tool when the agent declares skills.

The available-skills section lists every skill regardless of activation state. It includes routing guidance and workspace paths but never inlines skill markdown:

```text
- research: Research unfamiliar topics before answering with confidence.
  (path: /workspace/skills/research/SKILL.md)
```

When a request matches a skill description or the user names a skill, the model calls `load_skill`. Eve reads `SKILL.md` from the active sandbox at `/workspace/skills/<id>/SKILL.md`, strips YAML frontmatter, and returns the body as the tool result.

<ParamField body="skill" type="string" required>
Skill name or id from the Available skills block.
</ParamField>

Loading a skill adds instructions only. It does not register new tools. Typed runtime behavior still comes from `agent/tools/`.

### Skill authoring shapes

| Shape | Path | Seeded to workspace |
| --- | --- | --- |
| Flat markdown | `agent/skills/<name>.md` | `/workspace/skills/<name>/SKILL.md` |
| Packaged directory | `agent/skills/<name>/SKILL.md` + siblings | Full directory under `/workspace/skills/<name>/` |
| Module-backed | `agent/skills/<name>.ts` with `defineSkill` | Generated `SKILL.md` and `files` entries |
| Dynamic | `agent/skills/<name>.ts` with `defineDynamic` | Resolved at runtime, materialized to sandbox |

Packaged sibling files (`references/`, `assets/`, `scripts/`) are not pasted into the prompt. The model inspects them with `bash`, `read_file`, `glob`, or `grep` after loading the skill.

At build time, `materializeWorkspaceResources` copies authored skills and sandbox workspace seeds into `.eve/compile/workspace-resources/<nodeId>/`. Eve seeds that tree into the runtime sandbox at session bootstrap. A sandbox workspace cannot define a `skills/` entry — Eve manages that path.

## Workspace visibility through sandbox tools

Eve does not inline the full authored tree into the prompt. Only two sources reach the runtime workspace:

- `agent/skills/` → `/workspace/skills/...`
- `agent/sandbox/workspace/**` → `/workspace/...`

Everything under `lib/` stays import-only and never mounts to the workspace.

The workspace section in the system prompt is a shallow overview: the live root (`/workspace`), sorted root entries, and guidance to verify paths with `bash` before claiming file availability. Deeper inspection is explicit tool work.

| Tool | Sandbox scope | Typical use |
| --- | --- | --- |
| `bash` | `/workspace` cwd | `ls`, `find`, `rg`, shell commands |
| `read_file` | Sandbox FS | Line-numbered text reads |
| `glob` | Sandbox FS | Pattern-based file discovery |
| `grep` | Sandbox FS | Regex search across files |
| `write_file` | Sandbox FS | File writes (read-before-write enforced) |

<Warning>
The workspace overview is not a file listing. If `bash` verification fails, the model should report the failure rather than answering from the overview alone.
</Warning>

Tools and `load_skill` proxy through the app runtime into the agent's single sandbox. `load_skill` itself runs in the app runtime but reads skill files from the sandbox.

## Dynamic capabilities with defineDynamic

When context depends on caller identity, tenant, channel metadata, or external data, use `defineDynamic` instead of static authoring. Import paths:

| Capability | Import | Authored under |
| --- | --- | --- |
| Instructions | `defineDynamic`, `defineInstructions` from `eve/instructions` | `agent/instructions/` |
| Skills | `defineDynamic`, `defineSkill` from `eve/skills` | `agent/skills/` |
| Tools | `defineDynamic`, `defineTool` from `eve/tools` | `agent/tools/` |

Resolvers receive a `DynamicResolveContext` with `ctx.session.id`, `ctx.session.auth`, `ctx.channel` metadata, and conversation `messages`.

### Resolver events

| Event | Dynamic instructions | Dynamic skills | Dynamic tools |
| --- | --- | --- | --- |
| `session.started` | Yes | Yes | Yes |
| `turn.started` | Yes | Yes | Yes |
| `step.started` | No | No | Yes |

Instructions and skills are restricted to session and turn boundaries because they feed the cache-sensitive system prompt. Dynamic tools can also resolve before each model call.

On a matching event, execution order is: channel adapter handler → stream-event hooks → dynamic resolvers. The tool loop reads the current tool set immediately before each model call.

### Dynamic instructions example

```ts title="agent/instructions/persona.ts"
import { defineDynamic, defineInstructions } from "eve/instructions";

export default defineDynamic({
  events: {
    "session.started": (_event, ctx) => {
      const plan = ctx.session.auth.current?.attributes.plan ?? "free";
      return defineInstructions({
        markdown: `The caller is on the ${plan} plan. Match the depth of your answers to it.`,
      });
    },
  },
});
```

Each resolver owns a slug-keyed slot. A later event for the same file replaces that slot. Session-scoped output persists for the session; turn-scoped output resets each turn.

### Dynamic skills example

```ts title="agent/skills/team_playbook.ts"
import { defineDynamic, defineSkill } from "eve/skills";
import { PLAYBOOKS } from "../lib/playbooks.js";

export default defineDynamic({
  events: {
    "session.started": (_event, ctx) => {
      const team = ctx.session.auth.current?.attributes.team;
      const markdown = team ? PLAYBOOKS[team] : undefined;
      return markdown ? defineSkill({ markdown }) : null;
    },
  },
});
```

Resolved skills are written to the sandbox and announced through the same **Available skills** formatter static skills use. A map return names skills `slug__key`; a single `defineSkill` return names the skill after the file slug.

Dynamic skill names cannot collide with authored skills or another resolver's output. The build/runtime rejects conflicts.

<Info>
Hooks observe stream events but cannot inject model context. Use `defineDynamic` in `agent/instructions/` or `agent/skills/` for runtime prompt contributions. Hooks can update channel state that resolvers read on the next event.
</Info>

## Subagent context isolation

Subagents are a context-control boundary when a task needs a different prompt, tool surface, or runtime environment.

### Built-in `agent` tool

Every agent ships an `agent` tool that delegates to a copy of itself:

- Shares the parent's sandbox, tools, connections, and instructions
- Starts with fresh conversation history and fresh `defineState` state
- Child file writes are immediately visible to the parent

The parent packs everything the child needs into `message`. The child never sees parent history.

### Declared subagents

A declared subagent under `agent/subagents/<id>/` is discovered as its own agent root. It inherits nothing from the root's authored slots.

| Slot | Built-in `agent` tool | Declared subagent |
| --- | --- | --- |
| Instructions | Copy of parent | Own `instructions.{md,ts}`, optional |
| Tools | Inherited | Own `tools/` |
| Connections | Inherited | Own `connections/` |
| Skills | Inherited | Own `skills/` (invisible to parent) |
| Sandbox | Shared with parent | Own `sandbox/` or framework default |
| Hooks | Inherited | Own `hooks/` (parent hooks do not fire) |
| State | Fresh | Fresh |
| History | Fresh child session | Fresh child session |

Each delegation spins up a child session and stream. The parent emits `subagent.called` with `childSessionId`; subscribe to `GET /eve/v1/session/:childSessionId/stream` to follow child progress.

When the child needs shared procedures, duplicate skill markdown under each subagent's `skills/` or share typed helpers through `lib/`. There is no cross-agent skill visibility.

Reach for a declared subagent when the task needs a different specialist surface. Reach for a skill when the root agent keeps its identity and only needs an optional procedure.

## Choosing a context lever

| Need | Use |
| --- | --- |
| Permanent identity and standing rules | `instructions.md` / `instructions.ts` |
| Build-time prompt composition from typed helpers | `instructions.ts` with `defineInstructions` |
| Optional procedures that should not bloat every turn | `agent/skills/` + `load_skill` |
| Per-caller instructions or skill sets | `defineDynamic` in `agent/instructions/` or `agent/skills/` |
| Per-session or per-step tool sets | `defineDynamic` in `agent/tools/` |
| File inspection or command execution | Sandbox tools against `/workspace` |
| Different prompt, tools, or sandbox for a subtask | Declared subagent under `agent/subagents/<id>/` |
| Parallel work on the same files with same tools | Built-in `agent` tool |
| Long sessions exceeding context window | `compaction.thresholdPercent` in `agent.ts` |

## Prompt assembly reference

`createResolvedRuntimeTurnAgent` calls `composeRuntimeBasePrompt` with the resolved agent's instructions, skills, connections, and workspace spec. Section order:

1. **Instructions** — `Instructions (<name>)\n<markdown>`
2. **Workspace** — shallow root-entry overview when `rootEntries` is non-empty
3. **Tool execution** — parallel batch guidance when tools are available
4. **Connections** — when connections are declared
5. **Available skills** — name, description, and `/workspace/skills/...` path per skill

Before each model call, the tool-loop appends dynamic instruction system messages and any pending dynamic skill announcement. Skill bodies activated via `load_skill` appear in tool-result history for that turn.

## Related pages

<CardGroup>
<Card title="Instructions and skills" href="/instructions-and-skills">
Author always-on instructions and on-demand skills, including `load_skill` activation and workspace seeding.
</Card>
<Card title="Default harness" href="/default-harness">
Built-in tools including `load_skill`, `bash`, and file ops that inspect the workspace.
</Card>
<Card title="Sandbox" href="/sandbox">
Workspace seeding, sandbox backends, and proxy execution for shell and file tools.
</Card>
<Card title="Subagents and schedules" href="/subagents-and-schedules">
Declared subagents, the built-in `agent` tool, and delegation boundaries.
</Card>
<Card title="State, hooks, and context" href="/state-hooks-and-context">
`ctx.session`, `ctx.getSkill`, and where managed-context APIs are valid.
</Card>
<Card title="Configure agent.ts" href="/agent-configuration">
Compaction `thresholdPercent` and other settings that affect long-session context.
</Card>
</CardGroup>

---

## 09. Security model

> App runtime vs sandbox trust boundaries, secret brokering, connection token handling, channel signature verification, and fail-closed route auth defaults.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/09-security-model.md
- Generated: 2026-06-16T19:25:15.893Z

### Source Files

- `docs/concepts/security-model.md`
- `docs/guides/auth-and-route-protection.md`
- `packages/eve/src/public/channels/index.ts`
- `packages/eve/src/public/connections/index.ts`
- `packages/eve/src/execution/runtime-context.ts`

---
title: "Security model"
description: "App runtime vs sandbox trust boundaries, secret brokering, connection token handling, channel signature verification, and fail-closed route auth defaults."
---

Eve agents run across two trust zones — the **app runtime** (trusted Node.js host with `process.env`, connections, and durable execution) and the **sandbox** (isolated `/workspace` where shell commands execute). Secrets, route auth, connection tokens, and webhook signatures are resolved and verified on the trusted side; the model sees only tool definitions and their return values.

```mermaid
flowchart TB
  subgraph ingress["Ingress (channels)"]
    HTTP["eveChannel / defineChannel"]
    Webhooks["Slack · GitHub · Telegram · Twilio"]
  end

  subgraph trusted["App runtime (trusted)"]
    RouteAuth["routeAuth walk"]
    Tools["defineTool execute()"]
    Connections["MCP / OpenAPI connections"]
    TokenCache["Per-step token cache"]
    Proxy["bash · read_file · write_file proxy"]
  end

  subgraph isolated["Sandbox (untrusted)"]
    Workspace["/workspace filesystem"]
    Shell["Shell commands only"]
  end

  HTTP --> RouteAuth
  Webhooks --> RouteAuth
  RouteAuth --> Tools
  RouteAuth --> Connections
  Tools --> TokenCache
  Connections --> TokenCache
  Proxy --> Shell
  Proxy --> Workspace
  Tools -.->|"secrets never cross"| Shell
```

## Trust boundaries

| Capability | App runtime | Sandbox |
| --- | --- | --- |
| `process.env` / secrets | Yes | No |
| Author Node.js code (`defineTool`, connections, state) | Yes | No |
| Network | Unrestricted (host policy) | Controlled by `SandboxNetworkPolicy` |
| Filesystem | App-owned paths | Isolated `/workspace` |

On Vercel, the app runtime is a Vercel Function; each sandbox is a [Vercel Sandbox](https://vercel.com/docs/sandbox) microVM with hardware-level isolation. On local backends, isolation strength depends on the chosen `defineSandbox` backend (Docker, microsandbox, just-bash).

Built-in `bash`, `read_file`, `write_file`, `glob`, and `grep` tools are implemented in the app runtime and **proxy** into the sandbox. When the model calls `write_file`, the handler runs in the trusted host and forwards the write to `/workspace`. When it calls a custom `charge_card` tool, `execute` runs in the app runtime, reads `process.env.STRIPE_KEY`, calls Stripe, and returns `{ ok: true }` — the key never enters the sandbox or the model transcript.

<Warning>
Never pass secrets into sandbox environment variables or workspace files. Route privileged network calls through `defineTool`, `defineMcpClientConnection`, or credential brokering.
</Warning>

## Credential brokering

When the model needs authenticated egress from the sandbox — for example `git clone` of a private repo or an authenticated `curl` — and no tool or connection covers the call, Eve can inject auth headers at the sandbox **network firewall** so the secret stays in the app runtime.

On the Vercel Sandbox backend, call `sandbox.setNetworkPolicy()` with per-domain `transform` rules that add headers at the fetch boundary:

```ts title="Per-domain header injection"
const sandbox = await ctx.getSandbox();
await sandbox.setNetworkPolicy({
  allow: {
    "github.com": [{ transform: [{ headers: { authorization: "Basic ..." } }] }],
    "*": [],
  },
});
```

The sandbox process sees only the HTTP response; the bearer never appears in command output or workspace files. The GitHub channel's checkout flow uses this pattern to broker an installation token onto `github.com` and `codeload.github.com` egress.

| Backend | `setNetworkPolicy` support |
| --- | --- |
| `vercel()` | Full policy: allow-lists, deny-all, header transforms |
| `microsandbox()` | Local broker path with Vercel-style transforms |
| `docker()` | Coarse `"allow-all"` / `"deny-all"` only |
| `just-bash()` | Rejects `setNetworkPolicy` — no brokering surface |

See [Sandbox](/sandbox) for backend selection and policy authoring.

## Connection and tool credentials

Connection and tool auth is **outbound**: it signs the agent into external services (OAuth MCP servers, REST APIs) after route auth has already admitted the inbound caller.

### Token resolution

Connections declare `auth` on `defineMcpClientConnection` / `defineOpenAPIConnection`; individual tools can declare `auth` on `defineTool`. At runtime, `ctx.getToken()` and `ctx.requireAuth()` resolve the bearer:

<ParamField body="getToken()" type="Promise&lt;TokenResult&gt;">
Checks the per-step token cache, then invokes the authored `getToken` callback. With an interactive strategy (for example `connect("oauth/linear")` from `@vercel/connect/eve`), a cache miss parks the turn on a framework-owned OAuth callback URL.
</ParamField>

<ParamField body="requireAuth()" type="never">
Throws `ConnectionAuthorizationRequiredError` to trigger the same consent flow before any outbound call runs.
</ParamField>

Calling either accessor on a tool that does not declare `auth` throws at runtime.

### Per-step cache, never durable

Tokens are cached per workflow step, keyed by `(scope, principalKey)` so concurrent users on one session never share a bearer. The cache is a virtual context slot — **not serialized into durable workflow state**:

- Scoped by connection name or tool path-derived name
- Partitioned by resolved principal (`user:${issuer}:${id}` vs app-scoped strategies)
- Wiped between steps; cross-step reuse is delegated to the upstream provider (for example Connect's server-side cache)
- Evicted on downstream `401` so re-authorization does not re-read a stale token

The model never receives connection tokens. Tool `execute` and MCP client code inject them into outbound requests on the trusted side.

## Channel signature verification

Channels are the agent's ingress surface. Platform channels verify inbound webhooks before deriving caller identity; custom channels should follow the same contract.

### Built-in verifiers

| Channel | Mechanism | Replay protection |
| --- | --- | --- |
| Slack | HMAC-SHA256 over `v0:{timestamp}:{body}` (`X-Slack-Signature`) | 5-minute timestamp skew |
| GitHub | HMAC-SHA256 over raw body (`X-Hub-Signature-256`) | — |
| Twilio | HMAC-SHA1 over URL + sorted form params (`X-Twilio-Signature`) | — |
| Telegram | Constant-time compare of `X-Telegram-Bot-Api-Secret-Token` | — |

All HMAC comparisons use `timingSafeEqual` — never `===` on signature strings.

Each channel also accepts an optional `webhookVerifier` callback for forwarded traffic (for example Connect-authenticated webhooks verified with Vercel OIDC instead of the platform signing secret).

### Identity derivation rules

<Check>
Two rules apply to every channel — built-in and custom:
</Check>

1. **Verify signatures in constant time** over the raw request body (or delegate to a trusted verifier).
2. **Do not trust body-supplied identity.** Derive `principalId` from fields parsed only *after* verification succeeds — for example Slack's `team_id` + `user_id` from a signed payload, or Twilio's `From` from verified form params. A `principalId` claimed in an unauthenticated JSON body is attacker-controlled and enables cross-user impersonation.

Custom dashboard-style webhooks should authenticate the raw body with HMAC, compare in constant time, and only then map verified fields to `SessionAuthContext`.

## Route auth (fail-closed defaults)

Route auth is **inbound** HTTP protection on the Eve channel. It runs at the channel layer before any model work starts.

### Protected routes

`eveChannel({ auth })` guards these routes via `routeAuth`:

| Route | Auth required |
| --- | --- |
| `POST /eve/v1/session` | Yes |
| `POST /eve/v1/session/:sessionId` | Yes |
| `GET /eve/v1/session/:sessionId/stream` | Yes |
| `GET /eve/v1/health` | No — always public for load balancers |

Custom `defineChannel` routes should call `routeAuth(request, auth)` to reuse the same walk semantics, or emit `createUnauthorizedResponse(...)` for hand-rolled checks.

### Ordered auth walk

`auth` accepts a single `AuthFn` or an ordered array. Each entry has three outcomes:

| Return value | Effect |
| --- | --- |
| `SessionAuthContext` | Accept request; halt walk |
| `null` / `undefined` | Skip to next entry |
| Throw `UnauthenticatedError` / `ForbiddenError` | Reject with structured 401 / 403 |

If every entry skips — including `auth: []` — the request gets **401**. Admitting anonymous callers requires an explicit final `none()` entry.

```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [localDev(), vercelOidc()],
});
```

### Shipped verifiers

| Helper | Use when |
| --- | --- |
| `localDev()` | Loopback hostname or `vercel dev` (`VERCEL=1` + `VERCEL_ENV=development`) |
| `vercelOidc()` | Vercel deployment; verifies bearer JWT against Vercel OIDC issuer |
| `httpBasic()` | Shared username/password for operators |
| `jwtHmac()` / `jwtEcdsa()` | Custom JWT signers |
| `oidc()` | Arbitrary OIDC issuer |
| `none()` | Explicit anonymous access (must be final entry) |
| `placeholderAuth()` | Scaffold guardrail — structured 401 in production until replaced |

<Warning>
`localDev()` keys off the request URL hostname, not bare `process.env.VERCEL`. An origin that trusts attacker-controlled `Host` headers can be spoofed. Layer a real authenticator in production; never rely on `localDev()` alone.
</Warning>

`eve init` scaffolds `[localDev(), vercelOidc(), placeholderAuth()]`. In production, `placeholderAuth()` throws `UnauthenticatedError` with code `eve_production_auth_not_configured` so half-configured apps fail closed instead of serving open routes. Replace it with your app's `AuthFn` before browser traffic arrives. Deleting `agent/channels/eve.ts` falls back to `[localDev(), vercelOidc()]`, which still rejects production browser callers.

Route-auth secrets (`ROUTE_AUTH_BASIC_PASSWORD`, JWT signing keys) live in environment variables and are re-materialized at boot — never in compiled `.eve/` artifacts.

`createIpAllowList(...)` and `isIpAllowed(...)` can drop requests before auth and runtime execution based on client IP.

### Session auth propagation

After route auth succeeds, `buildRunContext` seeds `AuthKey` and `InitiatorAuthKey` into the runtime context:

| Field | Meaning |
| --- | --- |
| `ctx.session.auth.current` | Caller on the active inbound turn |
| `ctx.session.auth.initiator` | Caller that started the durable session |

Follow-up messages update `auth.current` but leave `auth.initiator` pinned. Both are `null` only on internal paths (subagents, schedules) that never traversed an authored route. Use these principals to scope tools, resolve dynamic capabilities, or enforce tenant boundaries — there is no second per-session ownership ACL beyond route auth.

## Authored markdown is data

Skills, schedules, and instructions authored as markdown carry YAML frontmatter parsed strictly as data. Eve disables gray-matter's built-in `javascript` / `js` frontmatter engines (which would `eval()` on parse). A `---js` or `---javascript` fence throws `"JavaScript frontmatter is not supported."` instead of executing. Frontmatter must parse to a plain YAML object.

## Pre-production checklist

<Steps>
<Step title="Replace placeholder auth">
Swap `placeholderAuth()` in `agent/channels/eve.ts` for a real `AuthFn` (`vercelOidc()`, `httpBasic()`, `oidc()`, or your own). Verify an unauthenticated production request returns `401`.
</Step>

<Step title="Verify channel signatures">
Set each platform channel's signing secret (`SLACK_SIGNING_SECRET`, GitHub webhook secret, `TWILIO_AUTH_TOKEN`, `TELEGRAM_WEBHOOK_SECRET_TOKEN`). Custom channels must verify HMAC in constant time and derive identity only after verification.
</Step>

<Step title="Keep secrets in process.env">
Never embed secrets in compiled artifacts or sandbox workspace files. Route privileged calls through tools, connections, or credential brokering.
</Step>

<Step title="Scope connection tokens">
Grant connections and tool `auth` strategies the least privilege required. Tokens reach external hosts on the trusted side, never the model.
</Step>

<Step title="Tighten sandbox egress">
Set a network policy tighter than `"allow-all"` when the model should not have open egress. Use credential brokering for authenticated egress to specific domains.
</Step>

<Step title="Escape untrusted UI text">
Model- or user-controlled strings rendered into channel UIs should be escaped for that surface.
</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Auth and deployment" href="/auth-and-deployment">
Route auth walk, env vars and secrets, `eve link` / `eve deploy`, and production verification.
</Card>
<Card title="Channels" href="/channels">
`defineChannel`, platform factories, webhook verification, and `eve channels` scaffolding.
</Card>
<Card title="Connections" href="/connections">
MCP and OpenAPI connections, OAuth callbacks, and `getToken` / `requireAuth` flows.
</Card>
<Card title="Sandbox" href="/sandbox">
`defineSandbox` backends, network policy, workspace seeding, and proxy execution.
</Card>
<Card title="Tools" href="/tools">
`defineTool` auth, HITL approval, and `requireAuth` on individual tools.
</Card>
</CardGroup>

---

## 10. Instructions and skills

> Author instructions.md or instructions.ts with defineInstructions, flat and packaged skills under agent/skills/, load_skill activation, and workspace seeding to /workspace/skills.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/10-instructions-and-skills.md
- Generated: 2026-06-16T19:26:22.944Z

### Source Files

- `docs/instructions.mdx`
- `docs/skills.mdx`
- `packages/eve/src/public/definitions/instructions.ts`
- `packages/eve/src/public/skills/index.ts`
- `packages/eve/src/compiler/normalize-skill.ts`
- `apps/fixtures/weather-agent/agent/skills/get-weather.md`

---
title: "Instructions and skills"
description: "Author instructions.md or instructions.ts with defineInstructions, flat and packaged skills under agent/skills/, load_skill activation, and workspace seeding to /workspace/skills."
---

Eve splits agent context into always-on instructions (`agent/instructions.md`, `agent/instructions.ts`, or `agent/instructions/`) and on-demand skills (`agent/skills/`). Instructions prepend to every model call; skills advertise descriptions in the system prompt and load full procedure text only when the model calls the framework-owned `load_skill` tool. At build time, Eve compiles both surfaces into `.eve/compile/`; at session bootstrap, skill files seed into `/workspace/skills/<name>/` inside the sandbox workspace.

## Instructions vs skills

| Surface | Authored path | When it enters context | Use for |
| --- | --- | --- | --- |
| Instructions | `agent/instructions.md`, `.ts`, or `instructions/` | Every turn, prepended to the system prompt | Permanent identity, tone, standing rules |
| Skills | `agent/skills/*` | On demand via `load_skill` tool result | Long or situational procedures |

<Note>
Instructions never execute code. Skills add instructions to context, not new execution surfaces — tools remain visible whether or not a skill is loaded. For typed runtime behavior, author `agent/tools/` instead.
</Note>

## Author instructions

The root agent requires instructions. Subagents may omit them. Identity is path-derived; do not add a `name` field to `defineInstructions`.

### Flat markdown

```md title="agent/instructions.md"
You are a concise assistant. Use tools when they are available.
```

Keep this file short and stable: identity, tone, and rules that apply on every turn.

### TypeScript with `defineInstructions`

Switch to a module when you need typed helpers, `lib/` code, or build-time values:

```ts title="agent/instructions.ts"
import { defineInstructions } from "eve/instructions";
import { buildInstructionsPrompt } from "./lib/prompts.js";

export default defineInstructions({
  markdown: buildInstructionsPrompt(),
});
```

<ParamField body="markdown" type="string" required>
Resolved prompt text. The only field on `InstructionsDefinition`.
</ParamField>

Module-backed static instructions execute once at build time. Eve captures the resulting markdown into the compiled manifest; the runtime serves the same prompt every session without re-running the module.

### Multi-file `instructions/` directory

For more than one file, add `agent/instructions/`. Eve reads entries non-recursively and accepts `.md` and `.ts` modules (including `defineDynamic` resolvers). Entries combine in alphabetical order by filename (`localeCompare`).

A flat root file and the directory can coexist: the root file's content comes first, then sorted directory entries. You cannot author both `instructions.md` and `instructions.ts` at the root — that pairing emits a `discover/slot-collision` diagnostic.

## Author skills

Eve discovers skills from `agent/skills/` as flat markdown, flat modules, or packaged directories. Skill identity comes from the path (`agent/skills/summarize.md` → skill `summarize`; `agent/skills/research/SKILL.md` → skill `research`). Authored definitions do not carry a `name` field.

Skills follow the Agent Skills `SKILL.md` convention, so skill packs authored for that standard port over as-is.

### Flat markdown skill

The smallest skill is a single `.md` file. The body is the procedure; the description routes activation.

```md title="agent/skills/get-weather.md"
---
description: Use the weather tool before answering forecast or temperature questions.
---

When the user asks about weather, temperature, or forecast conditions, call the `get_weather` tool before answering.
```

Flat markdown skills may omit `description` frontmatter. When they do, Eve advertises the first non-empty, non-code-fence line of the body (with leading `#`, `>`, `*`, or `-` markers stripped). If no such line exists, Eve falls back to `Instructions for the <name> skill.` — a weak routing hint, so add explicit `description` frontmatter when intent-based routing matters.

### Packaged skill

A packaged skill is a directory with `SKILL.md` plus optional sibling trees:

:::files
agent/skills/research/
├── SKILL.md
├── references/
├── assets/
└── scripts/
:::

Packaged `SKILL.md` must carry `description` frontmatter; there is no filename slug to fall back on. Eve recognizes `references/`, `assets/`, and `scripts/` subdirectories during discovery.

```md title="agent/skills/research/SKILL.md"
---
description: Research unfamiliar topics before answering with confidence.
---

When the task is novel or ambiguous, gather evidence first, then answer with the key facts and the remaining uncertainty.
```

### TypeScript with `defineSkill`

When markdown cannot express typed values, generated content, or inline sibling files:

```ts title="agent/skills/research.ts"
import { defineSkill } from "eve/skills";

export default defineSkill({
  description: "Research unfamiliar topics before answering with confidence.",
  markdown:
    "When the task is novel or ambiguous, gather evidence first, then answer with the key facts and the remaining uncertainty.",
  files: {
    "references/checklist.md": "# Checklist\n\n- Find primary sources.\n",
  },
});
```

<ParamField body="description" type="string" required>
Routing hint advertised in the Available skills block. Write it as the task that should trigger activation.
</ParamField>

<ParamField body="markdown" type="string" required>
Procedure body returned by `load_skill` (frontmatter stripped).
</ParamField>

<ParamField body="files" type="Record<string, string | Uint8Array>">
Optional package-relative sibling files. Eve writes each entry under `/workspace/skills/<name>/` at compile and session bootstrap.
</ParamField>

Start with plain markdown; move to `defineSkill` only when you hit its limits.

### Per-agent scope

Skills are scoped to the agent that declares them. A subagent's `skills/` are invisible to the root agent, and the reverse holds. There is no shared-skill mechanism — put shared executable helpers in `lib/`.

## `load_skill` activation

When an agent declares skills, Eve injects an **Available skills** section into the system prompt and exposes the framework `load_skill` tool. All skills are always listed regardless of activation state; active skill bodies are never re-injected into the system prompt (they arrive via the tool result), which keeps the system prompt stable across the session for prompt caching.

```mermaid
sequenceDiagram
  participant Model
  participant Harness
  participant load_skill
  participant Sandbox

  Harness->>Model: System prompt (instructions + Available skills block)
  Model->>load_skill: skill: "research"
  load_skill->>Sandbox: read /workspace/skills/research/SKILL.md
  Sandbox-->>load_skill: markdown body (frontmatter stripped)
  load_skill-->>Model: skill instructions as tool result
  Model->>Model: Follow loaded procedure on subsequent steps
```

<ParamField body="skill" type="string" required>
Available skill name or id. Choose from the Available skills block.
</ParamField>

The `load_skill` tool reads from the active sandbox, never runs inside it, and is only surfaced when the agent declares skills. With no skills, Eve does not advertise descriptions and the model has nothing to load.

<Warning>
If activation fails (unsafe id, missing file), the AI SDK forwards the error as a tool-error result. The Available skills block instructs the model to say so briefly and continue with the best fallback.
</Warning>

## Workspace seeding to `/workspace/skills`

Eve does not mount the full `agent/` tree into the sandbox. Only two sources land in the workspace:

- `agent/skills/**` → `/workspace/skills/<name>/...`
- `agent/sandbox/workspace/**` → `/workspace/...`

At compile time, `materializeWorkspaceResources` writes per-agent skill trees under `.eve/compile/workspace-resources/<nodeId>/skills/`. Flat markdown and module-backed skills normalize into a package directory with `SKILL.md` at the root; packaged skills copy their full directory tree. Sandbox templates prewarm with per-agent skill seed files.

| Authored shape | Sandbox path |
| --- | --- |
| `agent/skills/forecast.md` | `/workspace/skills/forecast/SKILL.md` |
| `agent/skills/research/SKILL.md` + siblings | `/workspace/skills/research/SKILL.md`, `/workspace/skills/research/references/...` |
| `defineSkill` with `files` | `/workspace/skills/<name>/<relativePath>` for each entry |

<Warning>
Authoring `agent/sandbox/workspace/skills/...` is rejected at compile time. Eve manages the `skills/` workspace entry; put skill content under `agent/skills/` instead.
</Warning>

Packaged sibling files are not pasted into the prompt. The model inspects them with `bash`, `read_file`, or `glob` when a loaded skill references them.

## Read skill files from tools and hooks

`load_skill` returns `SKILL.md` body text. To read packaged siblings from inside a tool or hook, use `ctx.getSkill(id)`:

```ts
const research = ctx.getSkill("research");
const checklist = await research.file("references/checklist.md").text();
```

The handle exposes `name` and lazy `file(relativePath)` accessors that read from the active sandbox. Call `ctx.getSkill()` only from authored runtime functions (tools, hooks, channel events) inside a managed runtime context with sandbox access.

## Dynamic instructions and skills

Static sources are the same on every session. When context depends on the caller (tenant, team, channel), wrap resolvers in `defineDynamic`:

- `agent/instructions/` — returns per-session system prompt via `defineInstructions`
- `agent/skills/` — returns the set of skills a caller can load via `defineSkill`

Both read `ctx.session.auth` or channel metadata. Dynamic skills reuse the same Available skills formatter for durable context announcements.

## Discovery diagnostics

Run `eve info` or `eve build` to surface discovery issues. Common skill and instruction failures:

| Code | Cause |
| --- | --- |
| `discover/required-instructions-missing` | Root agent has no `instructions.md`, `.ts`, or `instructions/` directory |
| `discover/slot-collision` | Both `instructions.md` and `instructions.ts` at the agent root |
| `discover/skill-collision` | Conflicting sources for the same skill id (e.g. `foo.md` and `foo/` directory) |
| `discover/skill-markdown-missing` | Packaged skill directory lacks `SKILL.md` |
| `discover/skill-frontmatter-invalid` | Invalid YAML frontmatter or missing required `description` on packaged `SKILL.md` |

## Recommended layout

<Steps>
<Step title="Write stable identity in instructions">
Keep `agent/instructions.md` (or `.ts`) focused on rules that apply every turn. Move long procedures out.
</Step>
<Step title="Add on-demand procedures as skills">
Author flat `.md` skills for simple cases; use packaged directories when you need `references/`, `assets/`, or `scripts/`.
</Step>
<Step title="Verify discovery">
Run `eve info` and confirm skills appear with sensible descriptions and no collision diagnostics.
</Step>
<Step title="Exercise activation in dev">
Run `eve dev`, send a request that matches a skill description, and confirm the model calls `load_skill` before following the procedure.
</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Context control" href="/context-control">
Always-on instructions vs on-demand skills, workspace visibility, and dynamic capabilities.
</Card>
<Card title="Default harness" href="/default-harness">
Built-in `load_skill` tool, compaction defaults, and framework tool overrides.
</Card>
<Card title="Project layout" href="/project-layout">
Authored slots under `agent/`, path-derived naming, and what compiles into `.eve/`.
</Card>
<Card title="Sandbox" href="/sandbox">
Workspace seeding, sandbox backends, and bootstrap lifecycle.
</Card>
<Card title="State, hooks, and context" href="/state-hooks-and-context">
`ctx.getSkill`, `ctx.getSandbox`, and where managed-context APIs are valid.
</Card>
<Card title="Tools" href="/tools">
Typed executable integrations — the counterpart to instruction-only skills.
</Card>
</CardGroup>

---

## 11. Tools

> defineTool schema and execute(ctx), HITL approval predicates (always/once/never), tool auth and requireAuth, built-in overrides, and dynamic tool resolution.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/11-tools.md
- Generated: 2026-06-16T19:26:17.069Z

### Source Files

- `docs/tools.mdx`
- `packages/eve/src/public/definitions/tool.ts`
- `packages/eve/src/public/tools/index.ts`
- `packages/eve/src/compiler/normalize-tool.ts`
- `apps/fixtures/weather-agent/agent/tools/get_weather.ts`
- `docs/guides/dynamic-capabilities.md`

---
title: "Tools"
description: "defineTool schema and execute(ctx), HITL approval predicates (always/once/never), tool auth and requireAuth, built-in overrides, and dynamic tool resolution."
---

A tool is a typed action the model can call — query a database, call an API, or trigger a business workflow. You author tools as TypeScript files under `agent/tools/`. They run in the **app runtime** with full access to `process.env` and your shared libraries, not inside the [sandbox](/sandbox). Built-in shell and file tools are the exception: they proxy work into the sandbox from the app runtime.

Eve never executes authored tools during discovery. The model sees descriptors first; only tools it actually calls run. Completed steps replay their recorded result on resume. A step interrupted mid-execution re-runs, so make non-idempotent side effects idempotent or gate them behind approval.

## Author a static tool

Place one tool per file under `agent/tools/`. The filename slug is the model-facing name — no `name` field on the definition.

```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 };
  },
});
```

Nested directories flatten into a single slug-safe segment: `agent/tools/billing/refund.ts` registers as `billing-refund`. Path separators never reach the model because most providers reject `/` in tool names.

### `defineTool` fields

<ParamField body="description" type="string" required>
  Model-facing summary of what the tool does.
</ParamField>

<ParamField body="inputSchema" type="Zod schema | Standard Schema | JSON Schema object" required>
  Validates and types the model's tool-call arguments. 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>`.
</ParamField>

<ParamField body="execute" type="(input, ctx) => TOutput | Promise<TOutput>" required>
  The implementation. May be sync or async.
</ParamField>

<ParamField body="outputSchema" type="Zod schema | Standard Schema | JSON Schema object">
  Optional schema describing the return value. With Zod or Standard Schema it also types the `execute` return. Code mode uses it to expose typed host-tool results to model-authored JavaScript.
</ParamField>

<ParamField body="needsApproval" type="(ctx) => boolean">
  Optional human-in-the-loop gate. See [Human-in-the-loop approval](#human-in-the-loop-approval).
</ParamField>

<ParamField body="auth" type="ToolAuthDefinition">
  Optional outbound authorization strategy. See [Tool auth](#tool-auth).
</ParamField>

<ParamField body="toModelOutput" type="(output) => ToolModelOutput">
  Optional projection controlling what the model sees as the tool result. See [Shape model output](#shape-model-output).
</ParamField>

`defineTool` stamps a brand that lifecycle code validates; raw object literals are rejected at compile time.

## The `ctx` parameter

`execute` receives a `ToolContext` — `SessionContext` plus token accessors when `auth` is declared.

| Member | Purpose |
| --- | --- |
| `ctx.session.id` | Active session identifier |
| `ctx.session.auth` | Caller snapshot from route auth (`current`, `initiator`) |
| `ctx.session.turn` | Active turn metadata |
| `ctx.session.parent` | Parent lineage when running inside a subagent |
| `ctx.getSandbox()` | Resolves the live sandbox handle |
| `ctx.getSkill(id)` | Reads a packaged skill's metadata and files |
| `ctx.getToken()` | Resolves the bearer for a declared `auth` strategy |
| `ctx.requireAuth()` | Forces the authorization flow before proceeding |

`getToken` and `requireAuth` throw when the tool does not declare `auth`. These APIs are live only inside active authored runtime execution (tools, hooks, channel handlers). Calling them at module top level throws.

Running in the app runtime is what lets a tool import shared code from `lib/`, read secrets from the environment, and participate in Eve's durable pause/resume model. See [State, hooks, and session context](/state-hooks-and-context) for the full context model.

## Human-in-the-loop approval

Gate sensitive tools with `needsApproval` and 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() }),
  needsApproval: always(),
  async execute(input) {
    return refund(input);
  },
});
```

| Helper | Behavior |
| --- | --- |
| `never()` | Never require approval (equivalent to omitting `needsApproval`). |
| `once()` | Require approval only the first time the tool runs in a session; auto-allow after an explicit approval. A denial leaves the tool unrecorded, so the next call prompts again. Keys off the bare tool name. |
| `always()` | Require approval before every call. |

For input-aware decisions, pass a custom predicate instead of a helper. It receives `{ toolName, toolInput, approvedTools }` and returns a boolean. `toolInput` can be undefined, so guard access:

```ts
needsApproval: ({ toolInput }) => (toolInput?.amount ?? 0) > 1000,
```

### How approval pause and resume works

Approvals and `ask_question` share one protocol:

1. The model requests input (approval or a question).
2. Eve emits an `input.requested` stream event carrying the pending requests.
3. The turn parks at `session.waiting`, durably.
4. The client answers with `inputResponses` (structured, keyed by `requestId`) or a normal follow-up `message`. A follow-up whose text matches an option label (case-insensitive) resolves automatically.

The run resumes exactly where it parked. Channels render the request — Slack turns approvals into buttons, Teams into Adaptive Cards. See [Sessions and streaming](/sessions-and-streaming) for the event and resume contract.

When a tool declares both `auth` and `needsApproval`, approval runs first. The user sees **approve, then sign in**. Eve records the approval before the sign-in park, so the tool is not re-prompted for approval after OAuth completes.

## Shape model output

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 a summary, project it with `toModelOutput`:

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

<ResponseField name="type" type="'text' | 'json'">
  `text` for a string summary; `json` for a smaller object.
</ResponseField>

<ResponseField name="value" type="string | unknown">
  The model-facing payload.
</ResponseField>

`toModelOutput` only affects what the model sees. Channel event handlers and hooks still receive the full output on `action.result`.

## Tool auth

When one tool calls a service behind OAuth or needs a bearer token, declare `auth` on the tool instead of wiring a separate connection. `auth` accepts the same shapes as connection auth:

- `connect("...")` from `@vercel/connect/eve` for Vercel Connect-backed OAuth
- A custom interactive definition via `defineInteractiveAuthorization`
- A plain `{ getToken }` object for static or pre-provisioned credentials

```ts title="agent/tools/list_okta_groups.ts"
import { defineTool } from "eve/tools";
import { connect } from "@vercel/connect/eve";
import { z } from "zod";

export default defineTool({
  description: "List the caller's Okta groups.",
  inputSchema: z.object({}),
  auth: connect("okta"),
  async execute(_input, ctx) {
    const { token } = await ctx.getToken();
    const res = await fetch("https://api.okta-proxy.internal/groups", {
      headers: { authorization: `Bearer ${token}` },
    });
    if (res.status === 401) ctx.requireAuth();
    return res.json();
  },
});
```

A `getToken`-only strategy defaults `principalType` to `"app"`. Interactive strategies use `principalType: "user"`. Set `displayName` on the `auth` definition to control the sign-in affordance label; it is presentation-only — the tool's path-derived name still keys the authorization scope, token cache, and callback URL.

`ctx.getToken()` checks the per-step token cache before invoking `getToken`. With an interactive strategy, a cache miss suspends the turn on a framework-owned callback URL, shows a sign-in affordance, and re-runs the tool after the OAuth callback completes.

`ctx.requireAuth()` throws `ConnectionAuthorizationRequiredError`, which the runtime converts into the same consent prompt. Map a downstream `401` to `requireAuth()` so Eve evicts the rejected token and re-challenges instead of returning a dead-token error to the model.

Route auth (who can reach your HTTP API) and tool auth (how your agent signs in to external services) are independent systems. See [Security model](/security-model) and [Connections](/connections) for trust boundaries and connection-level auth.

## Override or disable built-in tools

Every agent ships with built-in tools (`bash`, `read_file`, `write_file`, `glob`, `grep`, `web_fetch`, `web_search`, `todo`, `ask_question`, `agent`, `load_skill`, `connection_search`). See [Default harness](/default-harness) for the full set.

### Override

Author a tool at the same slug and it replaces the built-in. Spread the default from `eve/tools/defaults` to keep description, schema, and framework state (such as the `todo` tool's durable state key):

```ts title="agent/tools/write_file.ts"
import { defineTool } from "eve/tools";
import { writeFile } from "eve/tools/defaults";

export default defineTool({
  ...writeFile,
  async execute(input, ctx) {
    console.log("[write_file]", input.path);
    return writeFile.execute(input, ctx);
  },
});
```

Framework defaults are importable as `bash`, `readFile`, `writeFile`, `glob`, `grep`, `webFetch`, `webSearch`, `todo`, and `loadSkill`. To add a sandbox tool under a **new** name (without replacing a built-in), use the factory helpers: `defineBashTool`, `defineReadFileTool`, `defineWriteFileTool`, `defineGlobTool`, and `defineGrepTool` from `eve/tools`.

### Disable

Export a `disableTool()` sentinel from a file named after the tool slug:

```ts title="agent/tools/bash.ts"
import { disableTool } from "eve/tools";

export default disableTool();
```

If the filename matches no known framework tool, resolution fails at build time rather than silently doing nothing.

### Opt in to `Workflow`

The experimental `Workflow` tool stays off unless you re-export the opt-in marker:

```ts title="agent/tools/workflow.ts"
export { ExperimentalWorkflow as default } from "eve/tools";
```

With it enabled, the model can orchestrate subagents from model-authored JavaScript as one durable step.

## Dynamic tool resolution

When the right tool set is not known until runtime — it depends on the caller, tenant, feature flags, or external data — use `defineDynamic` instead of a static `defineTool` export. See [Context control](/context-control) for how dynamic capabilities fit the broader model.

```ts title="agent/tools/query.ts"
import { defineDynamic, defineTool } from "eve/tools";
import { z } from "zod";
import { listTables, runReadOnly } from "../lib/warehouse.js";

export default defineDynamic({
  events: {
    "session.started": async (_event, ctx) =>
      Object.fromEntries(
        (await listTables()).map((t) => [
          t.name,
          defineTool({
            description: `Query ${t.name}. Columns: ${t.columns.join(", ")}`,
            inputSchema: z.object({ sql: z.string() }),
            execute: ({ sql }) => runReadOnly(t.name, sql),
          }),
        ]),
      ),
  },
});
```

### Return shapes and naming

| Return shape | File | Tool name(s) |
| --- | --- | --- |
| Single `defineTool(...)` | `agent/tools/analytics.ts` | `analytics` |
| Map `{ export, query }` | `agent/tools/tenant.ts` | `tenant__export`, `tenant__query` |
| `null` | any | Contributes nothing for that event |

A map return always uses `slug__key`, even for a single entry, so adding a second tool later never renames the first.

### Resolver events

| Event | Resolver runs | Tools available for |
| --- | --- | --- |
| `session.started` | Once per session | Every model call in the session |
| `turn.started` | Once per turn | Every model call in the turn |
| `step.started` | Before each model call | That model call only |

When an event fires, the channel adapter handler runs first, then stream-event hooks, then dynamic resolvers. The tool loop reads the current set right before each model call. Resolvers across files run concurrently. The most recently fired event for a file replaces that file's earlier contribution.

### Inline `execute` requirement

Write `execute` as an inline function expression placed directly as the property value. The bundler transform does not detect `execute: myFn` or `execute: makeFn()`, so those tools work on the first step but do not survive replay after a crash or resume. On later steps the transform reconstructs each `execute` from stored closure variables.

`needsApproval` on dynamic tools is only honored for `step.started`-scoped entries whose live `execute` closures survive into the harness. Session- and turn-scoped dynamic tools replay from durable metadata and cannot carry a function across replay.

## Durability and idempotency

| Phase | Behavior |
| --- | --- |
| Discovery / `eve build` | Compiles descriptors; never runs `execute` |
| Model sees descriptors | Tool names, descriptions, and input schemas only |
| Tool call executes | `execute` runs in the app runtime |
| Step completes | Result is recorded durably |
| Resume / replay | Completed steps return the recorded result; interrupted steps re-run `execute` |

Treat charges, emails, and other irreversible side effects as idempotent, or gate them behind `needsApproval`.

## Verify tools are discovered

After authoring, confirm the tool appears in the agent manifest:

```sh
pnpm exec eve info
```

The output lists authored tools alongside framework defaults. Run `pnpm exec eve build` to surface compile-time errors such as invalid schemas, a `disableTool()` filename that matches no known default, or a dynamic resolver with a non-inline `execute`.

## Related pages

<CardGroup cols={2}>
  <Card title="Default harness" href="/default-harness">
    Built-in tools every agent ships with, and override or disable patterns.
  </Card>
  <Card title="Context control" href="/context-control">
    Dynamic capabilities, skills, and subagent context isolation.
  </Card>
  <Card title="Connections" href="/connections">
    MCP and OpenAPI tools with qualified names and connection-level auth.
  </Card>
  <Card title="State, hooks, and session context" href="/state-hooks-and-context">
    Full `ctx` model, `defineState`, and `defineHook` stream subscribers.
  </Card>
  <Card title="Sessions and streaming" href="/sessions-and-streaming">
    `input.requested` events, `inputResponses`, and reconnect behavior.
  </Card>
  <Card title="Security model" href="/security-model">
    App runtime vs sandbox trust boundaries and secret brokering.
  </Card>
  <Card title="Project layout" href="/project-layout">
    Path-derived naming rules and what compiles into `.eve/` artifacts.
  </Card>
  <Card title="TypeScript API" href="/typescript-api">
    Exported `define*` helpers, approval predicates, and built-in defaults.
  </Card>
</CardGroup>

---

## 12. Connections

> MCP and OpenAPI connections with defineMcpClientConnection and defineOpenAPIConnection, OAuth callback routes, connection_search qualified tool names, and getToken/requireAuth flows.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/12-connections.md
- Generated: 2026-06-16T19:27:02.024Z

### Source Files

- `docs/connections.mdx`
- `packages/eve/src/public/connections/index.ts`
- `packages/eve/src/compiler/normalize-connection.ts`
- `packages/eve/src/protocol/routes.ts`
- `packages/eve/src/runtime/session-callback-route.ts`
- `apps/fixtures/agent-tui-client/agent/connections/stub-mcp-user.ts`

---
title: "Connections"
description: "MCP and OpenAPI connections with defineMcpClientConnection and defineOpenAPIConnection, OAuth callback routes, connection_search qualified tool names, and getToken/requireAuth flows."
---

Connections under `agent/connections/` wire an Eve agent to external MCP servers and OpenAPI HTTP APIs. The compiler validates each module at `eve build`; the runtime re-imports live `auth` callbacks, discovers remote tools, brokers credentials the model never sees, and exposes them through the dynamic `connection__` tool namespace.

## Authoring and discovery

Connection modules export a default `defineMcpClientConnection` or `defineOpenAPIConnection` definition. The runtime name is path-derived from the filename slug — `agent/connections/linear.ts` registers as `"linear"`. Do not add a `name` field.

:::files
agent/
  connections/
    linear.ts          # MCP → name "linear"
    petstore.ts        # OpenAPI → name "petstore"
:::

At graph resolution, Eve injects a **Connections** system-prompt section listing each connection and its `description`. When the agent declares at least one connection, the framework registers a dynamic tool resolver with slug `connection`. The model-facing search tool is `connection__search`; discovered tools are callable by qualified name `connection__<connection>__<tool>` (for example `connection__linear__list_issues`).

<Steps>
<Step title="Search for tools">

Call `connection__search` with `keywords` (required), optional `connection` to scope one connection, and optional `limit` (default 10). Results include `qualifiedName`, `inputSchema`, and `needsAuthorization` when sign-in is required.

</Step>
<Step title="Call discovered tools">

In the next response, invoke the returned `qualifiedName` directly (for example `connection__linear__list_issues`). The resolver re-derives the tool set from conversation history on each step; after compaction, stale search results drop from the toolset.

</Step>
</Steps>

<Note>
The harness docs refer to `connection_search`; at runtime the model sees `connection__search` under the `connection__` namespace prefix.
</Note>

## MCP connections

`defineMcpClientConnection` from `eve/connections` points at an MCP server over Streamable HTTP or SSE.

```ts title="agent/connections/linear.ts"
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! }),
  },
});
```

Write `description` for the model — it appears in the system prompt and `connection__search` results.

### MCP definition fields

| Field | Purpose |
| ----- | ------- |
| `url` | MCP server HTTP endpoint (Streamable HTTP or SSE). |
| `description` | Model-facing summary of the connection and its tools. |
| `auth` | Optional authorization strategy (see [Authorization](#authorization)). |
| `headers` | Optional extra HTTP headers; stacks with `auth`. |
| `tools` | Optional filter: exactly one of `allow` or `block` on remote tool names. |
| `approval` | Optional per-connection approval gate (`never()`, `once()`, `always()` from `eve/tools/approval`). |

Filtered tools never appear in `connection__search` results.

## OpenAPI connections

`defineOpenAPIConnection` turns an OpenAPI 3.x or Swagger 2.0 document into one tool per operation.

```ts title="agent/connections/tfl.ts"
import { defineOpenAPIConnection } from "eve/connections";

export default defineOpenAPIConnection({
  spec: "https://api.tfl.gov.uk/swagger/docs/v1",
  description: "Transport for London Unified API from its public Swagger 2.0 document.",
  operations: { allow: ["Journey_Meta"] },
});
```

Each operation becomes `connection__<connection>__<operationId>`. When `operationId` is missing, Eve synthesizes `<method>_<sanitized-path>`.

### OpenAPI-specific fields

| Field | Purpose |
| ----- | ------- |
| `spec` | HTTPS URL fetched at runtime, or an inline parsed object. |
| `baseUrl` | Optional override for the document's first usable `servers` entry. |
| `operations` | Filter keyed on `operationId` (`allow` or `block`); mirrors MCP `tools`. |

`auth`, `headers`, and `approval` behave the same as MCP connections.

## Authorization

Eve sends resolved tokens as `Authorization: Bearer <token>`. Tokens are cached per workflow step keyed by `(connectionName, principalKey)` and never serialized into durable step payloads or conversation history.

### Principal types

<ParamField body="principalType" type='"app" | "user"' default="app for getToken-only auth">
Declares whether the connection acts as the agent (`"app"`) or on behalf of the end user (`"user"`).
</ParamField>

- **`"app"`** — one shared credential across all sessions. `getToken`-only auth defaults here when `principalType` is omitted.
- **`"user"`** — per-user tokens keyed by `issuer` + `id`. Required for interactive OAuth (`defineInteractiveAuthorization` pins `"user"`).

### Non-interactive `getToken`

A `getToken`-only `auth` object covers static API keys, pre-provisioned JWTs, and out-of-band OAuth. `getToken` runs before every connection tool call and returns `TokenResult`:

```ts
{ token: string; expiresAt?: number }  // expiresAt in ms since epoch
```

Set `expiresAt` when the token has a known TTL; Eve treats expired cache entries as misses. Throw `ConnectionAuthorizationRequiredError` to signal missing credentials — the runtime emits `authorization.required` without suspending the turn.

```ts title="No auth"
export default defineMcpClientConnection({
  url: "http://localhost:3001/mcp",
  description: "Local dev server.",
});
```

### Headers without Bearer auth

Use `headers` for API-key schemes or extra configuration. Headers stack on top of `auth`; do not set an `Authorization` header when `auth` is also provided.

### Interactive OAuth

For browser-based consent, provide all three methods via `defineInteractiveAuthorization` (or `connect()` from `@vercel/connect/eve`):

1. **`getToken`** — return a cached token or throw `ConnectionAuthorizationRequiredError` to start consent.
2. **`startAuthorization`** — runs in a durable step; returns a user-facing `challenge` and optional JSON-serializable `resume` (for example a PKCE verifier).
3. **`completeAuthorization`** — exchanges the IdP callback for a `TokenResult`.

`startAuthorization` and `completeAuthorization` are both-or-neither; providing exactly one is a definition error at `defineMcpClientConnection` / `defineOpenAPIConnection` time.

```ts title="Self-hosted OAuth"
import {
  ConnectionAuthorizationFailedError,
  ConnectionAuthorizationRequiredError,
  defineInteractiveAuthorization,
  defineMcpClientConnection,
} from "eve/connections";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace.",
  auth: defineInteractiveAuthorization<{ verifier: string }>({
    getToken: async ({ principal }) => {
      const token = await lookupCachedToken(principal);
      if (!token) throw new ConnectionAuthorizationRequiredError("linear");
      return { token };
    },
    startAuthorization: async ({ callbackUrl }) => {
      const verifier = makePkceVerifier();
      return {
        challenge: { url: buildAuthorizeUrl(callbackUrl, verifier) },
        resume: { verifier },
      };
    },
    completeAuthorization: async ({ resume, callback }) => {
      const token = await exchangeCode(resume!.verifier, callback.params.code!);
      return { token };
    },
  }),
});
```

#### Challenge fields

| Field | Purpose |
| ----- | ------- |
| `url` | Authorize URL for redirect or device flows. |
| `userCode` | Device code for device flows. |
| `instructions` | Call to action when no URL is available. |
| `displayName` | Provider label for channel sign-in UI (presentation only). |

`displayName` on the `auth` definition overrides a challenge-level value. Identity (scope, token cache, callback URL) stays keyed by the path-derived connection name.

#### Authorization errors

Throw these from `getToken` or `completeAuthorization`:

- **`ConnectionAuthorizationRequiredError(connectionName)`** — user must authorize; emits `authorization.required` and, for interactive strategies, parks the turn.
- **`ConnectionAuthorizationFailedError(connectionName, { reason?, retryable? })`** — authorization failed. `reason` is a stable machine-readable code on `authorization.completed` events. `retryable` defaults to `true`; set `false` for terminal cases like `access_denied`.

Narrow caught errors with `isConnectionAuthorizationRequiredError(err)` and `isConnectionAuthorizationFailedError(err)` (name-based guards that survive bundling).

### Vercel Connect

For managed OAuth with encrypted token storage and refresh:

```ts
import { connect } from "@vercel/connect/eve";
import { defineMcpClientConnection } from "eve/connections";

export default defineMcpClientConnection({
  url: "https://mcp.linear.app/sse",
  description: "Linear workspace.",
  auth: connect("linear"),  // Connect client UID
});
```

Connect-managed OAuth is user-scoped. The compiler duck-types a `vercelConnect: { connector }` marker onto the auth definition for build output without importing `@vercel/connect/eve` at compile time.

### Revoked tokens mid-call

`getToken` runs only before a tool call. When a downstream request returns `401` during `execute`, map it to `ctx.requireAuth()` (authored tools) or rethrow `ConnectionAuthorizationRequiredError` (connections). Eve evicts the rejected bearer from the per-step cache and optionally calls `auth.evict()` to purge strategy-level caches (for example Vercel Connect's in-process cache) before re-running consent.

### Approval plus authorization

When a connection declares both `approval` and interactive `auth`, the user sees **approve, then sign in**. Approval is recorded on session state before the OAuth park; after authorization resumes, the tool is not re-approved.

## OAuth callback route

Interactive connection authorization parks the turn until the IdP redirects back. Eve owns the callback under `/eve/v1` rather than exposing raw workflow webhook primitives.

:::endpoint GET /eve/v1/connections/:name/callback/:token
Receives OAuth IdP redirects (authorization code in query string). Also registered for `POST` to support `form_post` response modes.
:::

<ParamField path="name" type="string" required>
Path-derived connection name (for example `linear`).
</ParamField>

<ParamField path="token" type="string" required>
Unguessable workflow hook capability minted by `getHookUrl`. Authorizes the resume; the route is intentionally unauthenticated.
</ParamField>

The handler:

1. Parses query and form-encoded params into an `AuthorizationCallback` (params only — request headers are dropped).
2. Calls `resumeHook(token, { kind: "deliver", payloads: [{ authorizationCallback }] })` to wake the suspended turn.
3. Returns an "Authorization complete" landing page (or `404` when no pending authorization matches).

`getHookUrl(connectionName)` builds the full redirect URL as `{baseUrl}/eve/v1/connections/{name}/callback/{token}` using a per-session hook token derived from the session ID.

```mermaid
sequenceDiagram
  participant Model
  participant Runtime
  participant IdP
  participant Browser

  Model->>Runtime: connection__linear__list_issues
  Runtime->>Runtime: getToken throws Required
  Runtime->>Runtime: startAuthorization (durable step)
  Runtime-->>Browser: authorization.required + challenge.url
  Browser->>IdP: User consents
  IdP->>Runtime: GET /eve/v1/connections/linear/callback/:token
  Runtime->>Runtime: resumeHook → completeAuthorization
  Runtime->>Model: Tool result with fresh token
```

## `getToken` and `requireAuth` on authored tools

Authored tools under `agent/tools/` can declare the same `auth` shapes as connections. `ToolContext` then exposes:

- **`ctx.getToken()`** — resolves the bearer via the per-step cache, then the authored `getToken`. Cache miss on interactive strategies throws `ConnectionAuthorizationRequiredError` and parks the turn.
- **`ctx.requireAuth()`** — throws `ConnectionAuthorizationRequiredError` without resolving a token first; use to gate on sign-in or to re-challenge after a downstream `401`.

Calling either without an `auth` field on the tool throws. The scoped authorization machinery is identical to connections, keyed by the tool's path-derived name instead of a connection name. See [State, hooks, and session context](/state-hooks-and-context) for the full `ToolContext` surface.

## Compile-time behavior

`compileConnectionDefinition` imports each connection module during `eve build`, reads the protocol marker stamped by the `define*` factory (`"mcp"` or `"openapi"`), and writes serializable metadata to the compiled manifest. Live `auth` callbacks, OpenAPI `spec` objects, and operation filters are resolved at runtime by re-importing the authored module. Authoring errors surface at build time rather than on first request.

## Troubleshooting

| Symptom | Likely cause |
| ------- | ------------ |
| `connection__search` missing | Agent has no files under `agent/connections/`. |
| Connection in prompt but search returns errors | Remote server unreachable, invalid `url`/`spec`, or auth failure during `listTools`. |
| `Connection callback not pending` (404) | Callback arrived after the park expired, or token mismatch. |
| Infinite sign-in loop | Token rejected immediately after `completeAuthorization`; runtime fails with `token_rejected_after_authorization`. |
| `principal_required` | `principalType: "user"` connection invoked without an authenticated user session. |

Run `eve info` to verify connection discovery. Check `.eve/discovery/diagnostics.json` after `eve build` for compile-time validation errors.

## Related pages

<CardGroup>
<Card title="Project layout" href="/project-layout">
Where `agent/connections/` fits in the authored agent tree and path-derived naming rules.
</Card>
<Card title="Default harness" href="/default-harness">
Built-in `connection__search` registration and how discovered tools join the toolset.
</Card>
<Card title="Tools" href="/tools">
Authored tools, approval predicates, and per-tool `auth` with `getToken` / `requireAuth`.
</Card>
<Card title="Execution model" href="/execution-model">
Turn parking, durable steps, and resume semantics for OAuth and HITL.
</Card>
<Card title="HTTP API" href="/http-api">
Stable `/eve/v1` routes including connection OAuth callbacks.
</Card>
<Card title="Security model" href="/security-model">
Secret brokering, token handling, and trust boundaries for connections.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Route auth, Vercel Connect setup, and production verification.
</Card>
</CardGroup>

---

## 13. Channels

> HTTP and messaging ingress with defineChannel and platform factories (eve, slack, discord, teams, telegram, twilio, github), route verbs, webhook verification, and eve channels add/list scaffolding.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/13-channels.md
- Generated: 2026-06-16T19:26:56.217Z

### Source Files

- `docs/channels/overview.mdx`
- `docs/channels/custom.mdx`
- `packages/eve/src/public/channels/index.ts`
- `packages/eve/src/compiler/normalize-channel.ts`
- `packages/eve/src/internal/nitro/routes/channel-dispatch.ts`
- `packages/eve/src/cli/commands/channels.ts`

---
title: "Channels"
description: "HTTP and messaging ingress with defineChannel and platform factories (eve, slack, discord, teams, telegram, twilio, github), route verbs, webhook verification, and eve channels add/list scaffolding."
---

Channels are the ingress layer between external platforms and the Eve runtime. Each channel module under `agent/channels/` compiles into one or more Nitro-mounted routes; route handlers call `send()` to start or resume sessions, and event handlers deliver runtime output back to the platform. The framework enables the default Eve HTTP channel even when `agent/channels/eve.ts` is absent, and merges authored routes with framework defaults at build time.

## Channel contract

A channel adapter performs three jobs:

1. **Normalize** platform input into a user message (string or `UserContent`).
2. **Own the continuation token** — the channel-local resume handle for a conversation on that surface. The runtime namespaces tokens as `<channelName>:<rawToken>` (for example `slack:C0123ABC:1800000000.001234`).
3. **Deliver** responses — post messages, stream events, or acknowledge webhooks depending on the platform.

Local subagents do not declare channels. Only the root agent under `agent/channels/` registers ingress routes.

```mermaid
flowchart LR
  subgraph ingress["Ingress"]
    Platform["Platform webhook / client"]
    Route["Nitro channel route"]
  end
  subgraph runtime["Eve runtime"]
    Send["send()"]
    Session["Durable session"]
    Events["Channel event handlers"]
  end
  Platform --> Route
  Route --> Send
  Send --> Session
  Session --> Events
  Events --> Platform
```

## File location and identity

Channel files live at the root agent only:

:::files
agent/
  agent.ts
  channels/
    eve.ts          # optional override of default HTTP channel
    slack.ts
    intake.ts
:::

The file stem is the channel id — `agent/channels/intake.ts` is addressed as `intake`. Export the channel as the module's default export. Do not add a `name` field; Eve derives identifiers from the filesystem path.

Each route in a `defineChannel` `routes` array becomes a separate compiled manifest entry with its own `(method, urlPath)` pair. The compiler records the channel name from the path and the URL path from the route definition.

## Authoring with defineChannel

Import `defineChannel` and route helpers from `eve/channels`:

```ts
import { defineChannel, GET, POST } from "eve/channels";

export default defineChannel({
  routes: [
    POST("/message", async (req, { send }) => {
      const body = await req.json();
      const session = await send(body.message, {
        auth: null,
        continuationToken: body.token,
      });
      return Response.json({ sessionId: session.id });
    }),
    GET("/sessions/:sessionId/stream", async (_req, { getSession, params }) => {
      const session = getSession(params.sessionId);
      const stream = await session.getEventStream();
      return new Response(stream, {
        headers: { "content-type": "application/x-ndjson; charset=utf-8" },
      });
    }),
  ],
  events: {
    "message.completed"(event, channel, ctx) {
      // deliver completed messages back to the surface
    },
  },
});
```

### Route verbs

Declare HTTP routes with `GET`, `POST`, `PUT`, `PATCH`, and `DELETE`. Each handler receives the raw `Request` and a helpers object:

| Helper | Purpose |
| --- | --- |
| `send(message, options)` | Start or resume a session on this channel |
| `getSession(sessionId)` | Look up an existing session (for streaming) |
| `receive(targetChannel, input)` | Hand inbound work to another channel |
| `params` | Path parameters from `[name]` segments |
| `waitUntil(promise)` | Extend request lifetime for post-ack background work |
| `requestIp` | Client IP, or `null` when unavailable |

WebSocket routes use `WS(path, handler)`. The handler runs once per upgrade and returns lifecycle hooks (`upgrade`, `open`, `message`, `close`, `error`). For third-party SDKs that bind directly to a Node `http.Server`, `createWebSocketUpgradeServer()` provides a compatibility bridge.

### ChannelDefinition fields

<ParamField body="routes" type="RouteDefinition[]" required>
HTTP and WebSocket route descriptors. Required on every channel.
</ParamField>

<ParamField body="state" type="object">
Seeds durable adapter state for stateful channels. Passed to `send()` via `options.state` on first dispatch.
</ParamField>

<ParamField body="context(state, session)" type="function">
Builds the per-step `channel` argument for event handlers. Can close over `session` to call `setContinuationToken()` later.
</ParamField>

<ParamField body="events" type="ChannelEvents">
Lifecycle handlers keyed by stream event type (`message.completed`, `turn.started`, `input.requested`, etc.). `session.failed` receives only `(data, channel)` — no `ctx`.
</ParamField>

<ParamField body="receive(input, { send })" type="function">
Accepts cross-channel handoffs from `receive()` in another channel's route handler.
</ParamField>

<ParamField body="fetchFile(url)" type="function">
Fetches bytes for `FilePart.data` URL objects before sandbox staging. Return `null` to pass the URL through to the model provider.
</ParamField>

<ParamField body="metadata(state)" type="function">
Projects a JSON-safe subset of adapter state for instrumentation and dynamic resolvers (`ctx.channel.metadata`, narrowed with `isChannel`).
</ParamField>

### Continuation tokens

Pass the channel-local raw token to `send()`. The framework prepends the channel name before handing it to the runtime. Platform factories ship helpers such as `slackContinuationToken(channelId, threadTs)` and `twilioContinuationToken(from, to)`. Custom channels define their own join format.

Re-key a parked session with `channel.setContinuationToken(rawToken)` or `session.setContinuationToken(rawToken)` from a `context()` factory. Inbound deliveries addressed to the old token are dropped after the next step boundary.

## Default Eve HTTP channel

The Eve channel is the framework's default HTTP session API — the routes the terminal UI, `eve/client`, `useEveAgent`, and `curl` use. It is enabled by default without an authored file. Add `agent/channels/eve.ts` only to override behavior (most often route auth).

`eveChannel()` from `eve/channels/eve` wraps `defineChannel` and mounts:

| Method | Path | Behavior |
| --- | --- | --- |
| `POST` | `/eve/v1/session` | Create session; returns `202` with `sessionId` and `continuationToken` |
| `POST` | `/eve/v1/session/:sessionId` | Continue session with message and/or `inputResponses` |
| `GET` | `/eve/v1/session/:sessionId/stream` | NDJSON event stream (`startIndex` query param supported) |

When no authored `eve.ts` exists, the framework injects `eveChannel({ auth: [localDev(), vercelOidc()] })` — the same defaults the web scaffold writes.

```ts
import { eveChannel } from "eve/channels/eve";
import { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [localDev(), vercelOidc(), placeholderAuth()],
});
```

`auth` is required. Pass a single `AuthFn` or an ordered array walked by `routeAuth`: the first entry returning a `SessionAuthContext` wins; `null`/`undefined` skips to the next; exhaustion returns `401`. Include `none()` last for anonymous traffic.

## Platform channel factories

Built-in channels are `defineChannel` instances with platform-specific verification, parsing, and delivery. Default-export the factory result from `agent/channels/<name>.ts`.

| Factory | Import path | Default route | Verification |
| --- | --- | --- | --- |
| `eveChannel` | `eve/channels/eve` | `/eve/v1/session`, `/eve/v1/session/:sessionId`, `/eve/v1/session/:sessionId/stream` | `routeAuth` (JWT, OIDC, Basic, etc.) |
| `slackChannel` | `eve/channels/slack` | `POST /eve/v1/slack` | Slack v0 HMAC (`X-Slack-Signature`) or custom `webhookVerifier` |
| `discordChannel` | `eve/channels/discord` | `POST /eve/v1/discord` | Ed25519 (`X-Signature-Ed25519`) or `webhookVerifier` |
| `teamsChannel` | `eve/channels/teams` | `POST /eve/v1/teams` | Bot Framework JWT or `webhookVerifier` |
| `telegramChannel` | `eve/channels/telegram` | `POST /eve/v1/telegram` | `X-Telegram-Bot-Api-Secret-Token` or `webhookVerifier` |
| `twilioChannel` | `eve/channels/twilio` | `POST /eve/v1/twilio/messages`, `/voice`, `/voice/transcription` | `X-Twilio-Signature` over URL + sorted form params |
| `githubChannel` | `eve/channels/github` | `POST /eve/v1/github` | `X-Hub-Signature-256` HMAC or `webhookVerifier` |

<Note>
`linearChannel` (`eve/channels/linear`, `POST /eve/v1/linear`) is also shipped but is not in the platform factory list above. Author it the same way when you need Linear Agent Sessions.
</Note>

Each factory accepts an optional `route` override, `credentials` for secrets, inbound hooks (`onAppMention`, `onCommand`, `onComment`, etc.), `events` overrides, and `uploadPolicy` for inbound attachments. Platform channels that receive webhooks typically acknowledge immediately and drive `send()` inside `waitUntil()` so the platform does not time out.

### Webhook verification pattern

Platform verifiers share a contract:

- **Throw or return falsy** → channel responds `401`.
- **Return a string** → accept and use that string as the verified body.
- **Return other truthy** → accept and keep the original body.

All built-in verifiers also support an optional `webhookVerifier` callback for Connect-forwarded traffic authenticated with Vercel OIDC instead of the platform signing secret.

<Warning>
Twilio signs the exact public URL plus sorted POST parameters. Set `webhookUrl` when a proxy or tunnel rewrites `request.url`, or signature checks fail even with a valid auth token.
</Warning>

### Example: Slack channel

```ts
import { slackChannel } from "eve/channels/slack";

export default slackChannel({
  credentials: {
    botToken: process.env.SLACK_BOT_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET,
  },
});
```

The default route handles Slack Events API callbacks and `application/x-www-form-urlencoded` interaction payloads on the same path.

## Runtime dispatch

`dispatchChannelRequest` in the Nitro host looks up the channel by `(method, urlPath)` route key, builds `RouteHandlerArgs` (`send`, `getSession`, `receive`, `params`, `waitUntil`, `requestIp`), and invokes the compiled handler. Background tasks registered through `waitUntil` are forwarded to `event.waitUntil()` so webhook acknowledgements can return before session work completes.

Authored channels carry a `handler` field. Framework-internal routes (connection OAuth callbacks, session callbacks) use a `fetch`-only shape with `RouteContext.agent`.

At build time, `computeChannelRouteRegistrations` merges framework defaults first, then authored routes. An authored file with the same channel name as a framework default replaces that default entirely.

## Disabling framework routes

Export `disableRoute()` as the default export of `agent/channels/<name>.ts` to remove a framework default route whose logical name matches the file stem:

```ts
import { disableRoute } from "eve/channels";

export default disableRoute();
```

Valid targets are framework channel names (`eve`, connection callback routes, session callback routes). Disabling a non-framework name throws at build time.

## Cross-channel hand-off

Route handlers can pivot work onto another channel:

```ts
import { defineChannel, POST } from "eve/channels";
import slack from "./slack.js";

export default defineChannel({
  routes: [
    POST("/incident", async (req, args) => {
      const incident = await req.json();
      args.waitUntil(
        args.receive(slack, {
          message: `Investigate ${incident.reference}`,
          target: { channelId: "C0123ABC" },
          auth: { authenticator: "incidentio", principalType: "service", principalId: incident.actor.id, attributes: {} },
        }),
      );
      return new Response("ok");
    }),
  ],
});
```

The target channel's `receive` hook owns continuation-token format and initial state. Calling `receive` does not also start a session on the current channel.

## CLI scaffolding

### eve channels add

`eve channels add` composes channel selection, file scaffolding, Vercel services configuration, and optional deploy. Known kinds for non-interactive use:

<ParamField body="kind" type="slack | web" required>
Channel kind. Required when stdin is not a TTY or when `--yes` is passed.
</ParamField>

<ParamField body="--force" type="boolean">
Overwrite existing scaffold files.
</ParamField>

<ParamField body="--yes" type="boolean">
Assume yes for confirmations (e.g. Slackbot creation). Requires an explicit kind.
</ParamField>

```sh
eve channels add              # interactive picker (web, Slack)
eve channels add slack        # scaffold Slack channel + connector setup
eve channels add web          # scaffold Next.js web chat + agent/channels/eve.ts
```

The `web` kind scaffolds `agent/channels/eve.ts` with `[localDev(), vercelOidc(), placeholderAuth()]`, a Next.js chat UI, and Vercel services config. The `slack` kind scaffolds `agent/channels/slack.ts` and provisions a Slack connector against the linked Vercel project.

### eve channels list

Lists channel module stems discovered under `agent/channels/` (flat `.ts`/`.mts` files and folder-based modules).

<ParamField body="--json" type="boolean">
Emit `{ "channels": ["eve", "slack", ...] }`.
</ParamField>

```sh
eve channels list
eve channels list --json
```

When no channels exist, the command suggests running `eve channels add`.

## Choosing a channel

| Goal | Channel |
| --- | --- |
| Browser chat, SDK clients, `curl` | Eve channel (default) + client hooks |
| Slack mentions, DMs, buttons | `slackChannel` |
| Discord slash commands, components | `discordChannel` |
| Microsoft Teams messages, Adaptive Cards | `teamsChannel` |
| Telegram bot messages | `telegramChannel` |
| SMS or speech-transcribed calls | `twilioChannel` |
| GitHub @mentions, PR review | `githubChannel` |
| Internal webhook, WebSocket, custom transport | `defineChannel` |

<Info>
Eve uses Chat SDK card-builder primitives for rich Slack messages but does not use the Chat SDK runtime. Wire channels against Eve's `defineChannel` API, not a Chat SDK adapter.
</Info>

## Troubleshooting

| Symptom | Likely cause |
| --- | --- |
| `401` on platform webhook | Missing or wrong signing secret; clock skew beyond 5 minutes; custom `webhookVerifier` returned falsy |
| `401` on `/eve/v1/session` | `routeAuth` exhausted without a winner; replace `placeholderAuth()` in production |
| Route not found (`404`) | Route path or method mismatch; run `eve info` or `eve build` and check compiled channel entries |
| `disableRoute()` build error | File stem does not match a framework channel name |
| Twilio signature mismatch | `webhookUrl` does not match the URL Twilio configured |
| Session not resuming | Continuation token format changed; old-token deliveries dropped after re-key |

## Related pages

<CardGroup>
  <Card title="Sessions and streaming" href="/sessions-and-streaming">
    continuationToken contracts, NDJSON stream events, and follow-up routes on the Eve channel.
  </Card>
  <Card title="Security model" href="/security-model">
    Route auth, webhook verification, secret brokering, and fail-closed defaults.
  </Card>
  <Card title="Auth and deployment" href="/auth-and-deployment">
    `eveChannel` auth walk, env vars, `eve link`/`eve deploy`, and production verification.
  </Card>
  <Card title="HTTP API" href="/http-api">
    Stable `/eve/v1` request and response shapes for session create, continue, and stream.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    Full `eve channels` flags, exit codes, and the edit-info-dev-build-start loop.
  </Card>
  <Card title="TypeScript API" href="/typescript-api">
    Exported `define*` helpers, `AuthFn` strategies, and channel factory types.
  </Card>
</CardGroup>

---

## 14. Sandbox

> defineSandbox backends (local vs vercel), workspace seeding from agent/sandbox/workspace, bootstrap/onSession lifecycle, build-time prewarm, and proxy execution for shell/file tools.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/14-sandbox.md
- Generated: 2026-06-16T19:27:26.481Z

### Source Files

- `docs/sandbox.mdx`
- `packages/eve/src/public/definitions/sandbox.ts`
- `packages/eve/src/compiler/normalize-sandbox.ts`
- `packages/eve/src/execution/sandbox/prewarm.ts`
- `packages/eve/src/compiler/workspace-resources.ts`
- `docs/guides/deployment.md`

---
title: "Sandbox"
description: "defineSandbox backends (local vs vercel), workspace seeding from agent/sandbox/workspace, bootstrap/onSession lifecycle, build-time prewarm, and proxy execution for shell/file tools."
---

Every Eve agent owns exactly one isolated bash environment rooted at `/workspace`. The harness runs in the app runtime; shell and file operations execute inside the sandbox backend. Built-in `bash`, `read_file`, `write_file`, `glob`, and `grep` tools proxy through shared executors (`executeBashOnSandbox`, `executeReadFileOnSandbox`, and siblings) that resolve a live `SandboxSession` from the runtime context. Authored tools call `ctx.getSandbox()` for the same handle. Override behavior with `defineSandbox` only when you need setup hooks, seeded files, a pinned backend, or network policy.

## Architecture

```mermaid
flowchart TB
  subgraph harness["App runtime (harness)"]
    tools["Framework tools: bash, read_file, write_file, glob, grep"]
    authored["Authored tools via ctx.getSandbox()"]
    proxy["requireSandboxSession() → SandboxAccess.get()"]
    tools --> proxy
    authored --> proxy
  end

  subgraph lifecycle["Sandbox lifecycle"]
    prewarm["backend.prewarm() at build/dev"]
    create["backend.create() per durable session"]
    bootstrap["bootstrap({ use }) — template-scoped"]
    onSession["onSession({ use, ctx }) — session-scoped"]
    prewarm --> bootstrap
    create --> onSession
  end

  subgraph backends["SandboxBackend"]
    vercel["vercel() — Vercel Sandbox"]
    docker["docker() — local Docker"]
    micro["microsandbox() — local VM"]
    justbash["justbash() — pure-JS fallback"]
    default["defaultBackend() — availability chain"]
  end

  proxy --> create
  create --> backends
  prewarm --> backends
```

<Note>
Authored tool `execute` functions run in the app runtime with full `process.env`. Only sandbox-backed operations cross the trust boundary into `/workspace`.
</Note>

## Default tools and proxy execution

The model receives shell and file access through built-in tools. Each tool handler stays in the harness; execution delegates to sandbox primitives:

| Tool | Sandbox executor | Working directory |
| --- | --- | --- |
| `bash` | `executeBashOnSandbox` | `/workspace` |
| `read_file` | `executeReadFileOnSandbox` | `/workspace` |
| `write_file` | `executeWriteFileOnSandbox` | `/workspace` |
| `glob` | `executeGlobOnSandbox` | `/workspace` |
| `grep` | `executeGrepOnSandbox` | `/workspace` |

`requireSandboxSession()` centralizes context lookup: it reads `SandboxKey` from the active async-local scope, calls `SandboxAccess.get()`, and throws when sandbox access is missing or unavailable. Framework tools import this preamble statically so Nitro bundles can trace them; backend bindings load lazily inside the execution layer.

`write_file` enforces a read-before-write guarantee: the model must call `read_file` on a path before overwriting it. `glob` and `grep` probe for `rg` once per session and fall back to POSIX `find`/`grep` when ripgrep is absent. `bash` tail-truncates stdout and stderr to shared output limits.

Author a shell tool with `defineBashTool` to reuse the same executor core:

```ts title="agent/tools/run_check.ts"
import { defineBashTool } from "eve/tools";

export default defineBashTool({
  description: "Run a one-off shell check in the workspace.",
});
```

## SandboxSession API

`ctx.getSandbox()` is async, takes no arguments, and works only inside authored runtime functions (tools, hooks, channel handlers). Relative paths resolve from `/workspace`; absolute paths pass through unchanged.

| Method | Behavior |
| --- | --- |
| `run({ command })` | Run one command, block until exit, return `{ stdout, stderr, exitCode }` |
| `spawn(options)` | Launch a long-running process; returns `SandboxProcess` with `stdout`/`stderr` streams, `wait()`, `kill()` |
| `readTextFile` / `writeTextFile` | UTF-8 (or specified encoding) text I/O; `readTextFile` supports 1-based line ranges |
| `readBinaryFile` / `writeBinaryFile` | Raw byte I/O |
| `readFile` / `writeFile` | Stream byte I/O |
| `removePath({ path, force, recursive })` | Delete files or directories |
| `resolvePath(path)` | Anchor a relative path to `/workspace/...` |
| `setNetworkPolicy(policy)` | Change egress mid-turn (backend-dependent) |

`sandbox.id` is a stable per-session identifier across reconnects. Use it as a cache key for per-session state that must outlive individual step executions.

Option types (`SandboxSpawnOptions`, `SandboxReadTextFileOptions`, and others) export from `eve/sandbox`.

## Workspace seeding

Mount authored files into `/workspace` at session start by placing them under `agent/sandbox/workspace/`. This requires the folder layout (`agent/sandbox/sandbox.ts`), not the top-level shorthand:

:::files
agent/sandbox/
  sandbox.ts                ← optional override
  workspace/
    schema.sql              ← lands at /workspace/schema.sql
    scripts/run.sh          ← lands at /workspace/scripts/run.sh
:::

At compile time, Eve copies `sandboxWorkspaces` and skill packages into `.eve/compile/workspace-resources/<nodeId>/`, hashes the tree, and records a `workspaceResourceRoot` on the compiled manifest. Prewarm and session create pass those files as `seedFiles` to the backend.

Every file under `workspace/` mirrors into the sandbox cwd with structure intact. Eve lists top-level entries in the model prompt automatically.

<Warning>
`agent/sandbox/workspace/skills/` is reserved. Skill discovery seeds `/workspace/skills/` from `agent/skills/`; authoring `workspace/skills/...` is rejected at compile time.
</Warning>

## Authoring with defineSandbox

`defineSandbox` from `eve/sandbox` is an identity helper that attaches types. `backend` is optional; when omitted, the runtime substitutes `defaultBackend()`.

Two discovery layouts exist:

| Layout | Path | Use when |
| --- | --- | --- |
| Shorthand | `agent/sandbox.ts` | Definition only, no seeded files |
| Folder | `agent/sandbox/sandbox.ts` + `workspace/` | Seeded files or folder organization |

If both exist, the folder layout wins. Subagents override independently at `subagents/<name>/sandbox.ts` (or the folder form) and do not inherit the parent's sandbox.

```ts title="agent/sandbox/sandbox.ts"
import { defineSandbox } from "eve/sandbox";
import { vercel } from "eve/sandbox/vercel";

export default defineSandbox({
  backend: vercel({ runtime: "node24", resources: { vcpus: 2 } }),
  revalidationKey: () => "repo-bootstrap-v1",
  async bootstrap({ use }) {
    const sandbox = await use();
    await sandbox.run({ command: "apt-get install -y jq" });
  },
  async onSession({ use }) {
    await use({ networkPolicy: "deny-all" });
  },
});
```

<ParamField body="backend" type="SandboxBackend | () => SandboxBackend">
Backend factory or value. Factory form is invoked lazily on first access and memoized for the process lifetime.
</ParamField>

<ParamField body="bootstrap" type="(input: SandboxBootstrapContext) => void | Promise<void>">
Template-scoped hook. Call `use(options?)` to open the template session before snapshot capture. Only template filesystem state carries into later sessions.
</ParamField>

<ParamField body="onSession" type="(input: SandboxSessionContext) => void | Promise<void>">
Durable-session-scoped hook. Runs once per session before steps use the sandbox. `input.ctx` exposes `session.id`, `auth`, and `turn`. Call `use(opts?)` to apply per-session backend options.
</ParamField>

<ParamField body="revalidationKey" type="() => string | Promise<string>">
Required when `bootstrap` is present and external inputs affect template output. Evaluated at compile/build time and frozen into compiled artifacts. Authored source and seed contents are tracked automatically.
</ParamField>

When no `agent/sandbox.ts` (or folder equivalent) exists, the framework auto-provides a default sandbox via `defaultBackend()`.

## Backends

A `SandboxBackend` implements `prewarm` (build-time template capture) and `create` (runtime session open/reattach). Eve ships four pinned factories plus an availability-aware default:

| Import | Backend name | Runs where |
| --- | --- | --- |
| `vercel()` from `eve/sandbox/vercel` | `vercel` | [Vercel Sandbox](https://vercel.com/docs/sandbox) |
| `docker()` from `eve/sandbox/docker` | `local` (Docker) | Local Docker daemon via CLI |
| `microsandbox()` from `eve/sandbox/microsandbox` | `local` (microsandbox) | Lightweight local VM |
| `justbash()` from `eve/sandbox/just-bash` | `local` (just-bash) | Pure-JS interpreter, no real binaries |
| `defaultBackend()` from `eve/sandbox` | Resolved at runtime | Availability chain |

### Local vs Vercel

<Tabs>
  <Tab title="Hosted Vercel">
    Pin with `vercel()` or omit `backend` and let `defaultBackend()` select Vercel when `process.env.VERCEL` is set. Local container/VM backends cannot run on Vercel infrastructure. Sessions idle-timeout (default 30 minutes on Vercel); Eve preserves the filesystem and resumes on the next message. Domain-level network policies and credential brokering are fully supported.
  </Tab>
  <Tab title="Local development">
    With `backend` omitted, `defaultBackend()` resolves in priority order: Docker (reachable daemon) → microsandbox (Apple Silicon macOS or glibc Linux with KVM) → just-bash (dependency-free fallback). Pin a backend unconditionally with `docker()`, `microsandbox()`, or `justbash()`. `vercel()` also works locally when Vercel credentials are configured.
  </Tab>
</Tabs>

`defaultBackend()` accepts a keyed options bag so each inner backend gets typed create options:

```ts title="agent/sandbox/sandbox.ts"
import { defaultBackend, defineSandbox } from "eve/sandbox";

export default defineSandbox({
  backend: defaultBackend({
    vercel: { networkPolicy: "deny-all", resources: { vcpus: 4 } },
    docker: { image: "ghcr.io/vercel/eve:latest" },
    microsandbox: { memoryMiB: 2048 },
  }),
});
```

### Backend notes

**Docker** — Default image `ghcr.io/vercel/eve:latest`. Eve creates `/workspace` and verifies Bash before authored `bootstrap`. Templates reuse across sessions when source, seeds, `revalidationKey`, and Docker options match. `eve dev` prunes stale template images in the background.

**microsandbox** — Closest local match to hosted Vercel Sandbox: snapshot templates, `vercel-sandbox` user, domain-level firewall. `eve dev` auto-installs the `microsandbox` package when missing; production fails with actionable install errors.

**just-bash** — No daemon or VM. Virtual filesystem under `.eve/sandbox-cache/`. No real binaries (`git`, `node`, package managers) and no network isolation. `eve dev` auto-installs `just-bash` when missing.

Custom backends implement the public `SandboxBackend` interface (`name`, `prewarm`, `create`). Types export from `eve/sandbox`.

## Lifecycle

```mermaid
stateDiagram-v2
  [*] --> TemplatePlan: compile discovers sandbox
  TemplatePlan --> Skipped: no bootstrap, no seeds
  TemplatePlan --> Prewarm: seeds and/or bootstrap
  Prewarm --> TemplateReady: backend.prewarm captures snapshot
  TemplateReady --> SessionCreate: first message in durable session
  SessionCreate --> OnSession: onSession({ use, ctx }) once
  OnSession --> Live: steps proxy tools to SandboxSession
  Live --> Live: turns reuse persisted session state
  Live --> Resume: backend idle timeout / crash
  Resume --> Live: backend.create with existingMetadata
```

### bootstrap vs onSession

| Hook | Scope | When it runs | What persists |
| --- | --- | --- | --- |
| `bootstrap({ use })` | Template | Once when the template is built (prewarm) | Template filesystem and supported backend metadata |
| `onSession({ use, ctx })` | Durable session | Once per session, inside active runtime context | Per-session config (network policy, resources, credentials) |

Put reusable setup in `bootstrap` (clone baseline repo, install dependencies). Put per-session setup in `onSession` (network lockdown, per-user markers). Because `onSession` runs inside the harness, it can read `ctx.session.auth` without baking credentials into the template.

Network policy set on the backend factory applies before authored `bootstrap`. Policy set in `onSession`'s `use()` overrides per session. Call `sandbox.setNetworkPolicy(...)` on the live handle to change policy mid-turn.

### Template key strategies

`createRuntimeSandboxTemplatePlan` chooses how templates are keyed:

| Plan kind | Condition | Prewarm |
| --- | --- | --- |
| `none` | No `bootstrap`, no workspace seeds | Skipped (`templateKey` is `null`) |
| `workspace-content` | Seeds only | Keyed by workspace content hash |
| `bootstrap` | `bootstrap()` present | Keyed by `revalidationKey`, source hash, and content hash |

Template keys include `nodeId` so root and subagent sandboxes do not collide. Vercel stable templates key on `VERCEL_PROJECT_ID` so build-time prewarm and deployed runtime derive the same key.

## Build-time prewarm

Prewarm captures reusable sandbox templates before traffic arrives. `prewarmSandboxes` iterates every registered sandbox in the compiled agent graph and calls `backend.prewarm(...)` with `bootstrap`, `seedFiles`, and a stable `templateKey`.

<Steps>
  <Step title="Compile workspace resources">
    `materializeWorkspaceResources` writes `.eve/compile/workspace-resources/` and records content hashes on the manifest.
  </Step>
  <Step title="Collect prewarm targets">
    For each graph node with a sandbox, derive `templatePlan` and `templateKey`. Skip nodes where the plan is `none`.
  </Step>
  <Step title="Invoke backend.prewarm">
    Backends capture template state idempotently. Build logs report `reused` vs freshly built templates.
  </Step>
  <Step title="Runtime session create">
    `ensureSandboxAccess` calls `backend.create({ templateKey, sessionKey, existingMetadata })`. Missing templates throw `SandboxTemplateNotProvisionedError`; dev mode retries after on-demand prewarm.
  </Step>
</Steps>

### When prewarm runs

| Environment | Trigger | Failure behavior |
| --- | --- | --- |
| Vercel build | `VERCEL` and `VERCEL_DEPLOYMENT_ID` both set | Build fails (`runVercelBuildPrewarm`) |
| `eve dev` | Background prewarm on startup and after authored-source changes | Logged error; retried on first sandbox access |
| `eve start` (production) | `prewarmBuiltAppSandboxes` before serving | Startup failure |

Prewarm covers template construction only. `onSession()` still runs at runtime, once per durable session.

<Check>
Build logs show lines like `Eve: initialized N sandbox templates (X reused, Y built)`. A cache hit reports `reused: true` from the backend.
</Check>

## Network policy and credential brokering

Egress rules accept three forms:

```ts
networkPolicy: "allow-all"   // default
networkPolicy: "deny-all"    // block all egress, including DNS

networkPolicy: {
  allow: ["ai-gateway.vercel.sh", "*.github.com"],
  subnets: { deny: ["10.0.0.0/8"] },
}
```

Domain-level allow-lists and credential brokering work on `vercel()` and `microsandbox()`. Docker honors only `"allow-all"` and `"deny-all"`. just-bash rejects `setNetworkPolicy` entirely.

Secrets never enter the sandbox process. Per-domain `transform` injects headers at the firewall:

```ts
async onSession({ use }) {
  await use({
    networkPolicy: {
      allow: {
        "github.com": [{ transform: [{ headers: { authorization: "Basic your_base64_credentials_here" } }] }],
        "*": [],
      },
    },
  });
}
```

The `"*": []` catch-all keeps general egress open while `transform` applies only to `github.com`.

## Troubleshooting

| Symptom | Likely cause |
| --- | --- |
| `SandboxTemplateNotProvisionedError` | Template not prewarmed; dev retries automatically, production needs successful build prewarm |
| `agent/sandbox/workspace/skills/` discovery error | Reserved path; move skills to `agent/skills/` |
| `This tool requires sandbox access on the runtime context` | Tool called outside a managed harness step |
| `filePath must be an absolute path` | Model passed a relative path to `read_file`/`write_file`; use `/workspace/...` |
| Build fails during sandbox prewarm | `bootstrap()` threw or backend credentials missing; fix bootstrap or Vercel Sandbox access |
| just-bash / microsandbox missing in production | Optional peers not installed; `eve dev` auto-installs, production requires explicit install |

## Related pages

<CardGroup>
  <Card title="Security model" href="/security-model">
    App runtime vs sandbox trust boundaries, secret brokering, and fail-closed defaults.
  </Card>
  <Card title="Default harness" href="/default-harness">
    Built-in bash and file tools, compaction, and override patterns.
  </Card>
  <Card title="Project layout" href="/project-layout">
    `agent/sandbox/` discovery, path-derived naming, and `.eve/` compile artifacts.
  </Card>
  <Card title="Auth and deployment" href="/auth-and-deployment">
    Vercel build output, sandbox backend selection, prewarm constraints, and production verification.
  </Card>
  <Card title="State, hooks, and session context" href="/state-hooks-and-context">
    `ctx.getSandbox()`, `ctx.getSkill()`, and where managed-context APIs are valid.
  </Card>
  <Card title="Subagents and schedules" href="/subagents-and-schedules">
    Each subagent gets its own sandbox, independent of its parent.
  </Card>
</CardGroup>

---

## 15. Subagents and schedules

> Local subagents under agent/subagents/, remote agents with defineRemoteAgent, cron schedules (defineSchedule .ts and .md), dev schedule dispatch route, and nested delegation boundaries.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/15-subagents-and-schedules.md
- Generated: 2026-06-16T19:27:36.753Z

### Source Files

- `docs/subagents.mdx`
- `docs/schedules.mdx`
- `packages/eve/src/compiler/normalize-subagent.ts`
- `packages/eve/src/compiler/normalize-schedule.ts`
- `packages/eve/src/execution/subagent-adapter.ts`
- `packages/eve/src/internal/nitro/routes/dev-schedule-dispatch.ts`

---
title: "Subagents and schedules"
description: "Local subagents under agent/subagents/, remote agents with defineRemoteAgent, cron schedules (defineSchedule .ts and .md), dev schedule dispatch route, and nested delegation boundaries."
---

Eve exposes delegation and time-based execution as filesystem slots: local specialists under `agent/subagents/`, remote deployments via `defineRemoteAgent`, and cron schedules under `agent/schedules/`. The compiler lowers each subagent to a model-visible tool with a shared `{ message, outputSchema? }` input shape; schedules compile to Nitro task handlers that start durable sessions on their cron cadence (or on demand through the dev-only dispatch route).

## Delegation surfaces

| Surface | Location | Runtime behavior |
| ------- | -------- | ---------------- |
| Built-in `agent` tool | Shipped with every agent (override at `agent/tools/agent.ts`) | Copies the current agent; shares parent sandbox and tools |
| Local subagent | `agent/subagents/<id>/agent.ts` with `defineAgent` | Isolated child agent; own instructions, tools, skills, sandbox |
| Remote subagent | `agent/subagents/<id>/agent.ts` with `defineRemoteAgent` | Async `POST` to another Eve deployment; parent parks until callback |
| Schedule | `agent/schedules/<name>.{ts,md}` | Cron-fired session; markdown (task mode) or handler (`run`) |

All subagent kinds lower to the same tool input schema:

```ts
{
  message: string;       // all context the child needs; no parent history
  outputSchema?: object; // when set, child runs in task mode
}
```

Set `outputSchema` (on the definition or per call) to require structured output as the tool result.

## Built-in `agent` tool

Every agent receives an `agent` tool by default. The model calls it to delegate a subtask to a copy of the current agent:

- Shares the parent's sandbox, tools, connections, and instructions.
- Starts with fresh conversation history and fresh `defineState` data.
- File writes from parallel `agent` calls are immediately visible to the parent — useful for fan-out across files.
- If a declared subagent calls `agent`, the child is a copy of *that* subagent, not the root.

An authored tool at `agent/tools/agent.ts` takes priority over the built-in.

## Local subagents

A declared subagent lives under `agent/subagents/<id>/` and exports `defineAgent` from `agent.ts`. Its directory under `subagents/` is the only marker that makes it a subagent; the tool name is the path-derived `<id>` with no prefix (unlike connection tools).

<ParamField body="description" type="string" required>
Model-visible delegation hint on `defineAgent`. The compiler rejects subagents that omit it.
</ParamField>

:::files
agent/subagents/researcher/
├── agent.ts            # required (must export description)
├── instructions.md     # or instructions.ts, optional
├── tools/              # optional
├── skills/             # optional
├── connections/        # optional
├── hooks/              # optional
├── sandbox/            # optional
└── subagents/          # optional, nested subagents
:::

The compiler recurses depth-first through nested `subagents/` directories (`compileSubagentGraph`), producing a flat node list and parent→child edges. Each nested child is compiled as its own agent root with the same isolation rules.

<Warning>
Subagent names share the runtime tool namespace with authored tools. A subagent named `researcher` collides with `agent/tools/researcher.ts` — Eve rejects the build rather than picking a winner.
</Warning>

### Isolation boundary

A declared subagent inherits nothing from the root's authored slots. Discovery treats `agent/subagents/<id>/` as its own agent root; absent slots fall back to framework defaults, not the parent's versions.

| Slot | Built-in `agent` tool | Declared subagent |
| ---- | ----------------------- | ----------------- |
| Instructions | Inherited (copy) | Own `instructions.{md,ts}`, optional |
| Tools | Inherited | Own `tools/` |
| Connections | Inherited | Own `connections/` |
| Skills | Inherited | Own `skills/` |
| Sandbox | Shared with parent | Own `sandbox/`, else framework default |
| Hooks | Inherited | Own `hooks/` |
| State | Fresh | Fresh |
| Channels | Root-only | Root-only |
| Schedules | Root-only | Root-only |

`channels/` and `schedules/` are not supported inside local subagents. Duplicate shared procedures by copying skill markdown into each child's `skills/`, or share typed helpers via `lib/`.

## Remote subagents

Use `defineRemoteAgent` when the specialist is a separately deployed Eve agent, not a directory in your repo. The file still lives under `agent/subagents/<id>/`, but only `agent.ts` is allowed — the compiler rejects local package entries (`tools/`, `skills/`, `connections/`, etc.) on remote definitions.

```ts title="agent/subagents/weather.ts"
import { defineRemoteAgent } from "eve";
import { vercelOidc } from "eve/agents/auth";

export default defineRemoteAgent({
  url: "https://weather-agent.example.com",
  description: "Answers weather, temperature, forecast, wind, rain, and snow questions.",
  auth: vercelOidc(),
});
```

<ParamField body="url" type="string" required>
Base URL of the remote Eve deployment.
</ParamField>

<ParamField body="description" type="string" required>
Model-visible delegation description (same role as local subagents).
</ParamField>

<ParamField body="auth" type="OutboundAuthFn">
Outbound auth hook from `eve/agents/auth` (`vercelOidc`, `bearer`, `basic`).
</ParamField>

<ParamField body="path" type="string">
Route appended to `url` for the create-session request. Defaults to `/eve/v1/session`.
</ParamField>

<ParamField body="outputSchema" type="StandardSchema | JSON Schema">
Structured return type enforced by the remote in task mode.
</ParamField>

Remote dispatch is asynchronous:

1. Parent starts a task-mode session on the remote's `POST /eve/v1/session`, passing a framework callback URL.
2. Parent turn parks until the remote posts a terminal callback.
3. Parent resumes and surfaces the result as a tool outcome.

Failed starts return inline errors. Remotes that start then fail post a terminal failure callback. The parent stream carries `subagent.called`, `action.result`, and `subagent.completed` — for remote calls, `subagent.called.data.remote.url` records the target.

## Nested delegation boundaries

Each delegated subagent (local or remote) starts its own child session in **task mode** via `buildSubagentRunInput`. Key boundaries:

**Parent stream vs child stream.** The parent stream emits only control-plane events `subagent.called` and `subagent.completed`. Read `subagent.called.data.childSessionId` and subscribe at `GET /eve/v1/session/:childSessionId/stream` for full child progress.

**Lineage metadata.** `RunInput.parent` carries `callId`, `sessionId`, `turn`, and `rootSessionId`. Nested chains propagate `rootSessionId` from the top user-facing session so every descendant attributes to the same root in one hop.

**HITL proxying.** Child `input.requested` events route through `SUBAGENT_ADAPTER`, which forwards them to the parent via durable `resumeHook`. The parent channel renders HITL prompts; responses route back to the matching child by `childContinuationToken`. Concurrent nested descendants each own separate proxy entries on the parent.

**Sandbox sharing.** Only the built-in `agent` tool (self-delegation) forwards `parentSandboxState` and `sandboxSessionId` on the adapter state. Declared subagents get their own sandbox unless they author `sandbox/`.

**Capabilities.** Parent `SessionCapabilities` forward verbatim through the subagent chain so HITL readiness flows transparently.

```mermaid
sequenceDiagram
  participant Parent as Parent session
  participant Adapter as SUBAGENT_ADAPTER
  participant Child as Child session
  participant Hook as resumeHook

  Parent->>Child: dispatchRuntimeActionsStep (task mode)
  Parent-->>Parent: subagent.called (childSessionId)
  Child->>Adapter: input.requested (HITL)
  Adapter->>Hook: forward to parentContinuationToken
  Hook->>Parent: resume parked turn
  Parent-->>Parent: render HITL, collect response
  Parent->>Child: route response by childContinuationToken
  Child-->>Parent: subagent.completed
```

## Schedules

Schedules start the root agent on a cron cadence instead of waiting for inbound messages. Each schedule is a single file under `agent/schedules/`; the name is path-derived (`agent/schedules/billing/sweep.ts` → `"billing/sweep"`). Nested directories are supported. Schedules are **root-only** — declared subagents cannot author `schedules/`.

### `defineSchedule`

Import from `eve/schedules`. Provide `cron` and exactly one of `markdown` or `run`:

```ts
interface ScheduleDefinition {
  cron: string;
  markdown?: string; // fire-and-forget prompt (task mode)
  run?: (args: ScheduleHandlerArgs) => Promise<void> | void;
}

interface ScheduleHandlerArgs {
  receive: CrossChannelReceiveFn;
  waitUntil: (task: Promise<unknown>) => void;
  appAuth: SessionAuthContext;
}
```

<ParamField body="cron" type="string" required>
Standard 5-field cron (`minute hour day-of-month month day-of-week`). Minute granularity. On Vercel, expressions evaluate in UTC.
</ParamField>

`defineSchedule` is a type-level pass-through; the compiler enforces the one-of rule.

### Markdown form (fire-and-forget)

Minimal schedule: Eve runs the agent on the prompt and discards the output. The session uses `SCHEDULE_ADAPTER` in **task mode** — it runs to completion or fails, and cannot park for HITL or OAuth.

<Tabs>
  <Tab title="TypeScript">
```ts title="agent/schedules/heartbeat.ts"
import { defineSchedule } from "eve/schedules";

export default defineSchedule({
  cron: "*/5 * * * *",
  markdown: "Pull open Linear issues and POST a summary to the metrics endpoint.",
});
```
  </Tab>
  <Tab title="Markdown">
```md title="agent/schedules/cleanup.md"
---
cron: "0 0 * * 0"
---

Sweep stale workflow state.
```
  </Tab>
</Tabs>

Markdown files take `cron` in frontmatter; the body is the prompt.

### Handler form (`run`)

Use a handler when the schedule must deliver to a channel, branch on conditions, or compute arguments at fire time. Handler sessions use the same durable runtime as any other session and **can park** (for example, waiting for a Slack reply after `receive`).

```ts title="agent/schedules/daily-digest.ts"
import { defineSchedule } from "eve/schedules";
import slack from "../channels/slack.js";

export default defineSchedule({
  cron: "0 9 * * 1-5",
  async run({ receive, waitUntil, appAuth }) {
    waitUntil(
      receive(slack, {
        message: "Summarize yesterday's activity and post the digest.",
        target: { channelId: "C0123ABC" },
        auth: appAuth,
      }),
    );
  },
});
```

| Handler arg | Purpose |
| ----------- | ------- |
| `receive(channel, { message, target, auth })` | Start a session on another channel (same contract as route handlers) |
| `waitUntil(promise)` | Extend the cron task lifetime past handler return so parked sessions settle |
| `appAuth` | Pre-built app principal (`{ authenticator: "app", principalId: "eve:app", principalType: "runtime" }`) |

Wrap `receive` calls in `waitUntil` so Nitro awaits background work before the task ends.

### Dev schedule dispatch

`eve dev` never fires schedules on their cron cadence. Trigger one manually through the dev-only dispatch route, which runs the same path as production (`dispatchScheduleTask` → `ScheduleDispatcher.trigger`):

:::endpoint POST /eve/v1/dev/schedules/:scheduleId
Dev-only one-shot schedule dispatch. Re-resolves schedules from disk on every call so authored-source watcher edits apply without restart. No auth (local dev server only). Production builds never mount this route.
:::

<RequestExample>
```sh
curl -X POST http://localhost:3000/eve/v1/dev/schedules/heartbeat
```
</RequestExample>

<ResponseExample>
```json
{
  "scheduleId": "heartbeat",
  "sessionIds": ["sess_..."]
}
```
</ResponseExample>

`:scheduleId` is the path-derived schedule name. URL-encode `/` in nested names (`billing%2Fsweep`). Unknown ids return `404` with `availableScheduleIds`. Subscribe to each returned session at `GET /eve/v1/session/:sessionId/stream`.

### Production (Vercel)

Hosted builds register each compiled schedule as a Nitro scheduled task. The `cron` expression is written to `.vercel/output/config.json` as a Vercel Cron Job entry. Confirm discovery under **Settings → Cron Jobs** and execution under **Observability → Cron Jobs**.

<Note>
`eve dev` does not run cron. Use the dev dispatch route to exercise schedules locally.
</Note>

## When to use what

| Need | Use |
| ---- | --- |
| Same agent, parallel file work | Built-in `agent` tool |
| Different prompt, tools, or sandbox | Local subagent under `subagents/<id>/` |
| Specialist owned by another deployment | `defineRemoteAgent` |
| Optional procedure, same identity | Skill (`load_skill`) — lighter than a subagent |
| Periodic agent work | Schedule under `agent/schedules/` |
| Schedule that hands off to Slack/Discord/etc. | Schedule handler with `receive` + `waitUntil` |

## Related pages

<CardGroup>
  <Card title="Execution model" href="/execution-model">
    Session/turn/step nesting, durable checkpoints, and parked work (HITL, OAuth, subagents).
  </Card>
  <Card title="Sessions and streaming" href="/sessions-and-streaming">
    continuationToken contracts, NDJSON events, and child-session stream attachment.
  </Card>
  <Card title="Project layout" href="/project-layout">
    Authored slots, path-derived naming, and subagent inheritance rules.
  </Card>
  <Card title="Context control" href="/context-control">
    Subagent context isolation and on-demand skills.
  </Card>
  <Card title="Channels" href="/channels">
    Deliver schedule output to users via `receive`.
  </Card>
  <Card title="HTTP API" href="/http-api">
    Stable session routes and dev-only schedule-dispatch endpoint.
  </Card>
</CardGroup>

---

## 16. Configure agent.ts

> defineAgent fields: model (gateway id or LanguageModel), compaction thresholdPercent, modelOptions, experimental.codeMode, outputSchema, and build.externalDependencies packaging.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/16-configure-agent.ts.md
- Generated: 2026-06-16T19:28:43.061Z

### Source Files

- `docs/agent-config.md`
- `packages/eve/src/public/definitions/agent.ts`
- `packages/eve/src/compiler/normalize-agent-config.ts`
- `packages/eve/src/compiler/model-catalog.ts`
- `apps/fixtures/weather-agent/agent/agent.ts`

---
title: "Configure agent.ts"
description: "defineAgent fields: model (gateway id or LanguageModel), compaction thresholdPercent, modelOptions, experimental.codeMode, outputSchema, and build.externalDependencies packaging."
---

`agent/agent.ts` exports a default `defineAgent({ ... })` call that sets the agent's runtime configuration. Eve compiles this module at build time into `.eve/` manifest artifacts: model routing, compaction thresholds, experimental flags, structured output schemas, and hosted-build externals. Agent identity (`name`) is derived from `manifest.agentId` (package name or app-root basename); do not author a `name` field. Declare inbound auth and network policy on channels, not in `agent.ts`.

```text
agent/agent.ts  →  compileAgentConfig()  →  .eve/manifest (config.*)
                              ↓
                    runtime resolve-agent  →  harness sessions / turns
```

<Steps>
<Step title="Create or edit agent/agent.ts">

Export a default `defineAgent` call from `agent/agent.ts` (or `agent.mjs` / `agent.cts` / `agent.mts`). `eve init` scaffolds this file with a gateway model id.

```ts title="agent/agent.ts"
import { defineAgent } from "eve";

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

</Step>

<Step title="Verify discovery and compilation">

Run `eve info` to confirm the config module is discovered, then `eve build` to compile it into `.eve/`. Build fails closed when `model` is missing, unknown keys are present, or the AI Gateway catalog cannot resolve context-window metadata for compaction.

</Step>
</Steps>

## Omitting agent.ts

When no config module exists under `agent/`, Eve injects a synthetic default: `model: "anthropic/claude-sonnet-4.6"`. Once you add `agent.ts`, `model` is required — an empty export throws at compile time with `The "model" field is required.`

## Model

`model` selects the language model for agent turns. Accept either a gateway model id string or an AI SDK `LanguageModel` instance.

<CodeGroup>
```ts title="Gateway model id"
import { defineAgent } from "eve";

export default defineAgent({
  model: "anthropic/claude-opus-4.8",
});
```

```ts title="Direct provider instance"
import { anthropic } from "@ai-sdk/anthropic";
import { defineAgent } from "eve";

export default defineAgent({
  model: anthropic("claude-opus-4.8"),
});
```

```ts title="Provider options (weather fixture)"
import { defineAgent } from "eve";

export default defineAgent({
  model: "openai/gpt-5.5",
  modelOptions: {
    providerOptions: {
      openai: {
        reasoningEffort: "high",
        reasoningSummary: "auto",
      },
    },
  },
});
```
</CodeGroup>

<ParamField body="model" type="string | LanguageModel" required>
Gateway model id (`provider/model`) or an AI SDK-compatible `LanguageModel` (for example from `anthropic(...)`, `gateway(...)`, or another provider factory). Required when `agent.ts` is present.
</ParamField>

<ParamField body="modelContextWindowTokens" type="number">
Optional override for the primary model's context window size in tokens. Escape hatch when Eve cannot resolve metadata from the AI Gateway catalog (custom or unlisted models). When set, Eve skips the catalog lookup and uses this value verbatim for compaction threshold math.
</ParamField>

<ParamField body="modelOptions" type="AgentModelOptionsDefinition">
Provider option overrides forwarded to the AI SDK model runtime call. Currently supports `providerOptions`: a record keyed by provider slug with JSON-serializable option objects.
</ParamField>

### Routing at compile time

Eve classifies how the model reaches inference and embeds routing in the compiled manifest:

| Authored value | Routing | Runtime behavior |
| --- | --- | --- |
| Gateway id string (`anthropic/claude-sonnet-4.6`) | `gateway` | Routed through the Vercel AI Gateway |
| `gateway(...)` instance | `gateway` | Same as a string id |
| Direct provider instance (`anthropic(...)`) | `external` | Bypasses the gateway; talks to the provider endpoint |
| Gateway id + `providerOptions.gateway.byok` | `gateway` with `byok` | Forwards a bring-your-own-key provider block through the gateway |

<Note>
Compile time fetches the AI Gateway model catalog (cached at `.eve/cache/model-catalog.json`, 24-hour TTL) to embed `contextWindowTokens` for compaction. Build fails when metadata is unavailable and no `modelContextWindowTokens` override is set: `Cannot compile agent compaction because ... does not have known AI Gateway context window metadata.`
</Note>

Credential setup for gateway vs direct provider routing is covered on the installation page.

## Compaction

Compaction summarizes older turns when the conversation approaches the model's context window. The harness applies it automatically; tune when it triggers via the optional `compaction` block.

```ts title="agent/agent.ts"
export default defineAgent({
  model: "anthropic/claude-opus-4.8",
  compaction: {
    thresholdPercent: 0.75,
  },
});
```

<ParamField body="compaction.thresholdPercent" type="number (0–1)">
Fraction of the primary model context window that triggers compaction. Defaults to `0.9` at runtime when omitted. Lower values compact sooner.
</ParamField>

<ParamField body="compaction.model" type="LanguageModel">
Optional model used only for compaction summaries. When omitted, Eve uses the active turn model for the summary call.
</ParamField>

<ParamField body="compaction.modelContextWindowTokens" type="number">
Same escape hatch as `modelContextWindowTokens`, but for the compaction summary model.
</ParamField>

At session creation, Eve computes the token threshold as `floor(contextWindowTokens × thresholdPercent)` with a `recentWindowSize` of 10 turns preserved verbatim. See the default harness page for how compaction interacts with read-before-write tracking and todo re-injection.

## Experimental code mode

`experimental.codeMode` routes executable tools through a sandboxed code-execution wrapper (`code_mode` tool) instead of exposing them directly to the model. The model writes JavaScript that calls tools inside the sandbox.

```ts title="agent/agent.ts"
export default defineAgent({
  model: "openai/gpt-5.4",
  experimental: { codeMode: true },
});
```

<Warning>
`experimental` flags are unstable and may change or be removed in any release. Each agent node (root and every subagent) carries its own flags — enable code mode for the whole graph, only a subagent, or only the parent.
</Warning>

| Resolution | Effective value |
| --- | --- |
| `experimental.codeMode: true` | Code mode on |
| `experimental.codeMode: false` | Code mode off (overrides env) |
| Flag omitted | Falls back to `EVE_EXPERIMENTAL_CODE_MODE` env var; only `"1"` enables it |

When any node in the compiled graph enables code mode (or the `Workflow` tool), the hosted build bundles sandbox worker assets. Code mode requires a configured sandbox backend.

## Output schema

`outputSchema` declares a structured return type for task-mode runs: subagent delegation, schedule invocations, and remote jobs. Eve normalizes Standard Schema or JSON Schema objects to JSON Schema at compile time.

```ts title="agent/agent.ts"
import { z } from "zod";
import { defineAgent } from "eve";

const Report = z.object({
  title: z.string(),
  count: z.number().int(),
});

export default defineAgent({
  model: "openai/gpt-5.4-mini",
  outputSchema: Report,
});
```

Interactive conversation turns ignore the agent-level schema unless the client supplies a per-message `outputSchema` on `POST /eve/v1/session`. After a structured-output turn completes, the schema does not leak into subsequent plain turns.

## Build packaging

`build.externalDependencies` controls hosted-build packaging only. It does not affect prompts, tool execution, or runtime APIs.

```ts title="agent/agent.ts"
export default defineAgent({
  model: "openai/gpt-5.4-mini",
  build: {
    externalDependencies: ["sharp", "fixture-trace-only-dep"],
  },
});
```

<ParamField body="build.externalDependencies" type="string[]">
Package names Eve should keep external while compiling authored TypeScript modules (tools, channels, schedules, skills, instructions, connections, sandbox). Listed packages are traced into `server/node_modules` in hosted output instead of being inlined by the bundler. Prefer this for packages sensitive to bundling (native binaries, platform-specific artifacts).
</ParamField>

During compilation, Eve merges `externalDependencies` from the root agent down through the subagent graph and passes the merged list to every authored module compile step. The framework also auto-traces `@napi-rs/keyring` (pulled in transitively through Vercel OIDC auth) without requiring author configuration.

## Description (subagents)

`description` is optional on the root agent. Local subagents under `agent/subagents/<id>/agent.ts` must include `description` — Eve surfaces it as the lowered subagent tool's description so the parent can decide when to delegate. Build fails with a clear error when a subagent omits it.

## Field reference

| Field | Type | Default | Scope |
| --- | --- | --- | --- |
| `model` | `string \| LanguageModel` | `anthropic/claude-sonnet-4.6` when `agent.ts` omitted | Runtime turns |
| `modelContextWindowTokens` | `number` | catalog lookup | Compaction threshold math |
| `modelOptions` | `{ providerOptions? }` | none | Model call options |
| `compaction` | object | harness defaults (`thresholdPercent` → `0.9`) | Context management |
| `experimental.codeMode` | `boolean` | env backstop, else `false` | Tool exposure mode |
| `outputSchema` | Standard Schema or JSON Schema | none | Task-mode structured output |
| `build.externalDependencies` | `string[]` | none | Hosted build packaging |
| `description` | `string` | none | Subagent tool description (required for local subagents) |

`defineAgent` rejects unknown keys at normalization time. TypeScript checks the argument against `AgentDefinition` via `ExactDefinition`, so typos surface as compile errors in the editor.

## Adjacent configuration

| Concern | Location |
| --- | --- |
| System prompt | `agent/instructions.md` or `agent/instructions.ts` |
| Per-tool HITL approval | `agent/tools/*.ts` |
| Inbound auth and network policy | channel definitions (`agent/channels/`) |
| Sandbox and workspace | `agent/sandbox/` |
| Telemetry | `agent/instrumentation.ts` |
| Eval suites | `evals/` |

## Troubleshooting

| Symptom | Likely cause |
| --- | --- |
| `The "model" field is required.` | `agent.ts` exists but omits `model` |
| `does not have known AI Gateway context window metadata` | Unlisted gateway model id without `modelContextWindowTokens` override |
| `to provide a valid AI SDK language model` | Provider instance missing `provider`, `modelId`, `doGenerate`, or `doStream`; or unsupported `specificationVersion` |
| `missing a "description" field` | Local subagent `agent.ts` without `description` |
| `Expected ... to match the public Eve shape` | Unknown key, wrong type, or `thresholdPercent` outside `0–1` |

Run `eve info` for discovery diagnostics and `eve build` to surface compile-time validation errors before deployment.

## Related pages

<CardGroup>
<Card title="Project layout" href="/project-layout">
Where `agent.ts` lives in the authored tree and what compiles into `.eve/` artifacts.
</Card>
<Card title="Default harness" href="/default-harness">
How compaction, built-in tools, and the agent loop consume `agent.ts` config.
</Card>
<Card title="Subagents and schedules" href="/subagents-and-schedules">
Per-subagent `defineAgent` overrides, required `description`, and task-mode `outputSchema`.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Model credentials, `eve build` / `eve deploy`, and hosted `externalDependencies` tracing.
</Card>
<Card title="TypeScript API" href="/typescript-api">
Exported `defineAgent` types, `AgentDefinition`, and related `define*` helpers.
</Card>
</CardGroup>

---

## 17. Auth and deployment

> Route auth walk on eveChannel, env vars and secrets, eve link/deploy flows, Vercel build output (.vercel/output), sandbox backend selection, prewarm constraints, and production verification.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/17-auth-and-deployment.md
- Generated: 2026-06-16T19:28:24.997Z

### Source Files

- `docs/guides/auth-and-route-protection.md`
- `docs/guides/deployment.md`
- `packages/eve/src/cli/commands/link.ts`
- `packages/eve/src/cli/commands/deploy.ts`
- `packages/eve/src/internal/nitro/host/configure-nitro-routes.ts`
- `packages/eve/src/execution/sandbox/prewarm.ts`

---
title: "Auth and deployment"
description: "Route auth walk on eveChannel, env vars and secrets, eve link/deploy flows, Vercel build output (.vercel/output), sandbox backend selection, prewarm constraints, and production verification."
---

Production Eve agents run the same Nitro host and `/eve/v1` HTTP contract locally and on Vercel. `agent/channels/eve.ts` defines the inbound auth policy; `eve build` compiles `.eve/` artifacts and, when `VERCEL` is set, emits `.vercel/output`; `eve link` and `eve deploy` wire the project to Vercel and push a production deployment.

## Route auth on `eveChannel`

The default HTTP channel (`eveChannel` from `eve/channels/eve`) runs `routeAuth` on every session route before any model work starts. Authored in `agent/channels/eve.ts` (or omitted to fall back to the framework default).

| Route | Auth |
| --- | --- |
| `POST /eve/v1/session` | `routeAuth` walk |
| `POST /eve/v1/session/:sessionId` | `routeAuth` walk |
| `GET /eve/v1/session/:sessionId/stream` | `routeAuth` walk |
| `GET /eve/v1/health` | Always public (no walk) |

`GET /eve/v1/info` uses the framework default chain `[localDev(), vercelOidc()]` independently of the authored channel file.

### Ordered auth walk

`auth` accepts a single `AuthFn` or an ordered array. `routeAuth` walks entries until one accepts:

| Outcome | Behavior |
| --- | --- |
| Returns `SessionAuthContext` | Accept request; stop walk |
| Returns `null` / `undefined` | Skip to next entry |
| Throws `UnauthenticatedError` / `ForbiddenError` | Reject with structured 401 / 403 |
| Every entry skips (including `auth: []`) | Reject with 401 |

```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [localDev(), vercelOidc()],
});
```

Put application-specific providers first; entries that do not recognize the caller return `null` and fall through. To reject with a precise status instead of skipping:

```ts
import { ForbiddenError, UnauthenticatedError } from "eve/channels/auth";

throw new UnauthenticatedError({
  code: "authentication_required",
  message: "Sign in to continue.",
}); // 401

throw new ForbiddenError({ message: "Not allowed on this workspace." }); // 403
```

Custom channels built with `defineChannel` can call `routeAuth(request, auth)` to reuse the same semantics.

### Shipped verifier helpers

| Helper | Use when |
| --- | --- |
| `localDev()` | Local development on loopback hostnames (`localhost`, `*.localhost`, `127.0.0.0/8`, `::1`); also accepts `vercel dev` (`VERCEL=1` + `VERCEL_ENV=development`) |
| `vercelOidc()` | Vercel deployments; verifies bearer JWTs from Vercel OIDC |
| `none()` | Explicit anonymous access as the final entry |
| `httpBasic(...)` | Shared username/password for operators or services |
| `jwtHmac(...)` / `jwtEcdsa(...)` | Shared-secret or asymmetric JWT verification |
| `oidc(...)` | Arbitrary OIDC issuer |
| `placeholderAuth()` | Scaffold guardrail — see below |

<Warning>
`localDev()` keys off the request URL hostname, not bare `process.env.VERCEL`. Never rely on `localDev()` alone on public origins; layer a real authenticator in front.
</Warning>

### `vercelOidc()` acceptance rules

- Tokens whose `project_id` matches `VERCEL_PROJECT_ID` are always accepted (internal runtime and subagent callers need no enumeration).
- Tokens with `external_sub` authenticate as `principalType: "user"` when `project_id` matches `VERCEL_PROJECT_ID` and environment matches `VERCEL_TARGET_ENV` / `VERCEL_ENV`. Profile claims (`name`, `picture`, `email`) surface in `ctx.session.auth.current.attributes`.
- Tokens from other Vercel projects require an explicit `subjects` list (AWS IAM-style `*` wildcards). Build patterns with `vercelSubject({ teamSlug, projectName, environment? })` — `environment` defaults to `"production"`.

```ts
import { vercelOidc, vercelSubject } from "eve/channels/auth";

vercelOidc({
  subjects: [
    vercelSubject({ teamSlug: "partner", projectName: "data" }),
    vercelSubject({ teamSlug: "acme", projectName: "agent", environment: "*" }),
  ],
});
```

### Scaffold and framework defaults

`eve init` scaffolds:

```ts
import { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";

export default eveChannel({
  auth: [localDev(), vercelOidc(), placeholderAuth()],
});
```

`placeholderAuth()` returns `null` outside production. In production it throws a structured 401 (`code: "eve_production_auth_not_configured"`) so browser clients see a clear message instead of an internal error. Replace it before the first production browser request.

Deleting `agent/channels/eve.ts` falls back to the framework default `[localDev(), vercelOidc()]`, which also rejects unauthenticated production browser traffic.

### Caller snapshot in runtime

Route auth populates `ctx.session.auth`:

| Field | Meaning |
| --- | --- |
| `auth.current` | Caller on the active inbound turn |
| `auth.initiator` | Caller that started the durable session (pinned across follow-ups) |

Follow-up messages update `auth.current` but leave `auth.initiator` unchanged. Both are `null` only on internal paths (subagents) that never went through an authored route.

<Info>
Route auth decides access at the HTTP boundary. There is no second per-session ownership ACL stacked on top.
</Info>

## Environment variables and secrets

Set secrets in the Vercel project environment (or `.env.local` locally). Route-auth secrets and provider keys are never serialized into `.eve/` compiled artifacts; the runtime re-materializes them from the authored channel definition at boot.

### Model credentials

<ParamField body="VERCEL_OIDC_TOKEN" type="string">
Vercel OIDC token pulled by `eve link` / `vercel env pull`. Gateway model ids authenticate through Vercel OIDC with no provider keys to manage.
</ParamField>

<ParamField body="AI_GATEWAY_API_KEY" type="string">
Direct AI Gateway API key alternative to OIDC.
</ParamField>

<ParamField body="OPENAI_API_KEY" type="string">
Example direct provider key when not using the gateway.
</ParamField>

### Route auth secrets

Reference from `agent/channels/eve.ts` auth helpers — for example `ROUTE_AUTH_BASIC_PASSWORD` for `httpBasic(...)`, or JWT/OIDC signing keys for `jwtHmac` / `jwtEcdsa` / `oidc`.

### Vercel deployment context

| Variable | Role |
| --- | --- |
| `VERCEL` | Set during hosted builds and Vercel runtime; triggers `.vercel/output` emission and Vercel sandbox backend selection |
| `VERCEL_ENV` | `production`, `preview`, or `development`; used by `placeholderAuth()` and OIDC user-token environment matching |
| `VERCEL_TARGET_ENV` | Deployment target environment for OIDC user-token matching |
| `VERCEL_PROJECT_ID` | Current project binding for `vercelOidc()` and stable sandbox template scoping |
| `VERCEL_DEPLOYMENT_ID` | Enables build-time sandbox prewarm; keys source-graph templates |
| `VERCEL_AUTOMATION_BYPASS_SECRET` | Local bypass for Vercel preview protection when driving remote deployments with `eve dev <url>` |

<Note>
Do not set `VERCEL_TEAM_ID` at build time. Sandbox template keys must derive identically at build and runtime, and Vercel exposes no team variable at runtime.
</Note>

## `eve link` and `eve deploy`

### `eve link`

Interactive only — team and project pickers are the point of the command.

<Steps>
<Step title="Prerequisites">
Run from an Eve project root (`eve init` scaffold present).
</Step>
<Step title="Link">
```bash
eve link
```
Select team and project. Eve runs `vercel link`, then `vercel env pull` so a model credential lands in `.env.local`.
</Step>
<Step title="Verify">
Confirm `VERCEL_OIDC_TOKEN` or `AI_GATEWAY_API_KEY` appears in an env file. A running `eve dev` reloads env files automatically.
</Step>
</Steps>

Re-linking shows the current link and offers "Link to another project"; the new choice is authoritative.

In CI (non-TTY):

```bash
vercel link --project <name> --yes
vercel env pull
```

### `eve deploy`

Deploys to Vercel production (`vercel deploy --prod`), installing dependencies first and pulling environment variables after. Sets `VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1` for the deploy subprocess.

| Link state | TTY | Behavior |
| --- | --- | --- |
| Already linked | Any | Deploy immediately |
| Unlinked | Interactive | Walk `eve link` pickers, then deploy |
| Unlinked | Non-interactive | Exit with guidance to run `eve link` first |

```bash
eve deploy
```

On success the command prints the production URL when available.

## Build output

### `eve build`

```bash
eve build
```

Always writes Eve compiled artifacts under `.eve/`:

:::files
.eve/
├── discovery/
│   ├── agent-discovery-manifest.json
│   └── diagnostics.json
└── compile/
    ├── compiled-agent-manifest.json
    ├── compile-metadata.json
    └── module-map.mjs
:::

When `VERCEL` is set (every hosted Vercel build), `eve build` also writes the Vercel Build Output API bundle under `.vercel/output`. A plain local `eve build` skips that bundle.

### Vercel build sequence

```mermaid
flowchart TB
  subgraph build ["eve build (VERCEL set)"]
    A["compile .eve/ artifacts"] --> B["build Nitro app surface → .vercel/output"]
    B --> C["runVercelBuildPrewarm"]
    C --> D["build workflow flow surface"]
    D --> E["emit workflow functions into .vercel/output"]
    E --> F["emit agent-summary.json → .eve/"]
  end
```

Prewarm runs after the app Nitro surface is built but before workflow function bundling, so a prewarm failure aborts the build before spending time on function output that would never deploy.

`.eve/agent-summary.json` lives outside `.vercel/output` (not part of the deployable bundle).

Co-deployed Next.js apps may also write `.vercel/output/config.json` for route rewrites; see framework integration docs for `withEve` mounting.

## Sandbox backend selection

Attach a backend on the sandbox definition in `agent/sandbox/sandbox.ts` (or `agent/sandbox.ts`).

### Explicit Vercel backend

```ts title="agent/sandbox/sandbox.ts"
import { defineSandbox } from "eve/sandbox";
import { vercel } from "eve/sandbox/vercel";

export default defineSandbox({
  backend: vercel(),
});
```

### `defaultBackend()`

Omit `backend` and Eve falls back to `defaultBackend()` (`defaultSandbox`), which picks at first use:

| Priority | Backend | Condition |
| --- | --- | --- |
| 1 | `vercel()` | `process.env.VERCEL` is set |
| 2 | `docker()` | Docker daemon reachable |
| 3 | `microsandbox()` | Platform supported (Apple Silicon macOS or glibc Linux with KVM) |
| 4 | `justbash()` | Dependency-free fallback |

Selection is cached for the process lifetime. Pin a backend unconditionally with `vercel()`, `docker()`, `microsandbox()`, or `justbash()` when you need deterministic behavior.

<Warning>
If a Vercel sandbox template is not provisioned at runtime, Eve throws `SandboxTemplateNotProvisionedError` with guidance to run `eve build` or `prewarmAppSandboxes()` first.
</Warning>

## Build-time sandbox prewarm

During hosted Vercel builds, Eve prewarms reusable Vercel Sandbox templates so the first session avoids cold-start cost.

### When prewarm runs

Prewarm executes only when **both** `VERCEL` and `VERCEL_DEPLOYMENT_ID` are set (`shouldPrewarmVercelBuild()`). Dev runs and one-off local builds skip platform template provisioning.

### When a sandbox is skipped

`createRuntimeSandboxTemplateKey` returns `null` (no prewarm target) when the template plan is `kind: "none"`:

- No `bootstrap()` hook **and**
- No workspace seed files under `agent/sandbox/workspace/`

Sandboxes with only `onSession()` and no seeds do not get a prewarm template.

### Template keying

| Template kind | Keyed by |
| --- | --- |
| `workspace-content` (seed files only) | Skills and workspace file contents (`contentHash`) |
| `bootstrap` | Optional `revalidationKey()`, authored sandbox source hash, and seed contents |
| `source-graph` | Full source graph hash |

Each graph node (`nodeId`) produces a distinct template key so root and subagent sandboxes do not collide.

Build logs report each template as `reused cached` or `built`. Summary line: `initialized N sandbox templates (X reused, Y built)`.

<Check>
Prewarming covers template construction only. `onSession()` still runs at runtime, once per session.
</Check>

<Warning>
If build-time prewarm fails, the build fails. The same bootstrap would break at runtime.
</Warning>

## Production verification

<Steps>
<Step title="Build locally or on Vercel">
```bash
eve build
```
Confirm `.eve/discovery/diagnostics.json` has no blocking errors. On Vercel, confirm prewarm lines in the build log.
</Step>
<Step title="Deploy">
```bash
eve deploy
```
Or push to a Git-connected Vercel project.
</Step>
<Step title="Health probe">
```bash
curl https://<your-app>/eve/v1/health
```

<ResponseExample>
```json
{"ok":true,"status":"ready","workflowId":"..."}
```
</ResponseExample>
</Step>
<Step title="Create a session">
```bash
curl -X POST https://<your-app>/eve/v1/session \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer <token>' \
  -d '{"message":"Hello from production"}'
```

<ResponseExample>
```json
{"ok":true,"sessionId":"<id>","continuationToken":"eve:..."}
```
</ResponseExample>
</Step>
<Step title="Attach to the stream">
```bash
curl https://<your-app>/eve/v1/session/<sessionId>/stream \
  -H 'authorization: Bearer <token>'
```
</Step>
</Steps>

For interactive smoke tests against a remote deployment:

```bash
eve dev https://<your-app>
```

Set `VERCEL_AUTOMATION_BYPASS_SECRET` locally first when the deployment uses Vercel preview protection.

### Pre-production checklist

- [ ] `eve build` succeeds; writes `.vercel/output` when `VERCEL` is set
- [ ] Model credential and route-auth secrets set in Vercel env vars
- [ ] Sandbox backend matches environment (`vercel()` or `defaultBackend()`)
- [ ] Build-time prewarm reused or built templates without failing
- [ ] `placeholderAuth()` replaced with real policy
- [ ] `eve deploy` succeeds
- [ ] Health, session, and stream routes respond on the deployment URL

## Related pages

<CardGroup>
<Card title="Security model" href="/security-model">
Trust boundaries, secret brokering, and the pre-production security checklist.
</Card>
<Card title="Channels" href="/channels">
`defineChannel`, platform factories, and webhook verification beyond the default Eve HTTP channel.
</Card>
<Card title="Sandbox" href="/sandbox">
Workspace seeding, `bootstrap`/`onSession` lifecycle, and proxy execution for shell/file tools.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full `eve link`, `eve deploy`, and `eve build` flag and artifact reference.
</Card>
<Card title="HTTP API" href="/http-api">
Request/response shapes, NDJSON stream events, and stable route inventory.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Discovery diagnostics, 401 failure modes, and build/prewarm errors.
</Card>
</CardGroup>

---

## 18. State, hooks, and session context

> defineState get/update persistence, defineHook stream subscribers, ctx.session/getSandbox/getSkill/getToken/requireAuth, and where managed-context APIs are valid.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/18-state-hooks-and-session-context.md
- Generated: 2026-06-16T19:29:19.953Z

### Source Files

- `docs/guides/state.md`
- `docs/guides/hooks.md`
- `docs/guides/session-context.md`
- `packages/eve/src/public/context/index.ts`
- `packages/eve/src/public/definitions/hook.ts`
- `packages/eve/src/compiler/normalize-hook.ts`

---
title: "State, hooks, and session context"
description: "defineState get/update persistence, defineHook stream subscribers, ctx.session/getSandbox/getSkill/getToken/requireAuth, and where managed-context APIs are valid."
---

Eve runs authored runtime code inside an AsyncLocalStorage-scoped context container. `defineState`, `defineHook`, and `ctx.*` accessors resolve against that container and throw with `No active Eve context` when called outside a managed execution scope. Durable values—including author-defined state slots—serialize at workflow step boundaries and survive crashes, redeploys, and multi-day sessions.

## Durable state with `defineState`

Import `defineState` from `eve/context`. Declare handles at module scope so every importer shares the same slot.

```ts title="agent/lib/budget.ts"
import { defineState } from "eve/context";

export const budget = defineState("my-agent.budget", () => ({ count: 0, cap: 25 }));
```

<ParamField body="name" type="string" required>
Stable string identifier. Namespace to your agent (for example `myapp.budget`). Must not start with the reserved `eve.` prefix — that prefix is reserved for framework context keys and collisions silently corrupt serialization.
</ParamField>

<ParamField body="initial" type="() => T" required>
Function that produces the starting value on first access within a context.
</ParamField>

`defineState` returns a `StateHandle<T>`:

<ResponseField name="get()" type="T">
Reads the current value. Returns `initial()` on first access within the active context.
</ResponseField>

<ResponseField name="update(fn)" type="void">
Replaces the value with `fn(current)`.
</ResponseField>

Both methods require an active Eve context. Use them from tools, hooks, channel handlers, and other framework-invoked callbacks:

```ts title="agent/tools/spend.ts"
import { defineTool } from "eve/tools";
import { z } from "zod";
import { budget } from "../lib/budget.js";

export default defineTool({
  description: "Run a query, counting it against the session budget.",
  inputSchema: z.object({ sql: z.string() }),
  async execute({ sql }) {
    const { count, cap } = budget.get();
    if (count >= cap) throw new Error("Query budget exhausted for this session.");
    budget.update((s) => ({ ...s, count: s.count + 1 }));
    return runQuery(sql);
  },
});
```

### Persistence model

Author-defined state slots register as durable `ContextKey` entries. At each step boundary the runtime calls `serializeContext`, persisting every durable key in the active container into the workflow checkpoint. On resume, `deserializeContext` hydrates a fresh container from the checkpoint.

<Note>
Values must be JSON-safe. Author-defined keys have no custom codec; the serializer stores them as-is.
</Note>

State does not reset between turns by default. To clear per turn, overwrite from a lifecycle hook:

```ts title="agent/hooks/reset-budget.ts"
import { defineHook } from "eve/hooks";
import { budget } from "../lib/budget.js";

export default defineHook({
  events: {
    async "turn.started"() {
      budget.update(() => ({ count: 0, cap: 25 }));
    },
  },
});
```

### Subagent isolation

`defineState` values never cross the parent/child boundary. Every subagent—built-in `agent` copy or declared specialist—starts with fresh state, even when it is a copy of the same agent definition.

### State vs external storage

| Concern | `defineState` | External store / connection |
| --- | --- | --- |
| Scope | One session | Cross-session, cross-user |
| Lifetime | Dies with the session | Independent of conversation |
| Use case | Running counters, in-conversation plans, glossaries | Durable records, shared data, queryable tables |

## Stream hooks with `defineHook`

Hooks subscribe to the runtime NDJSON event stream from `agent/hooks/`. They run observe-only side effects after each event is durably recorded.

```ts title="agent/hooks/audit.ts"
import { defineHook } from "eve/hooks";

export default defineHook({
  events: {
    async "session.started"(_event, ctx) {
      console.info("session started", { sessionId: ctx.session.id });
    },
    async "message.completed"(event) {
      console.info("model finished", { length: event.data.message?.length ?? 0 });
    },
  },
});
```

Import `defineHook`, `HookDefinition`, `HookContext`, and related types from `eve/hooks`.

### Path-derived slug

The hook slug is the path-relative basename under `agent/hooks/`:

| File | Slug |
| --- | --- |
| `agent/hooks/audit.ts` | `audit` |
| `agent/hooks/auth/load-profile.ts` | `auth/load-profile` |

Discovery preserves lexicographic slug ordering when building the runtime registry.

### Event subscribers

The `events` map keys handlers by stream event type. Use `*` to match every event.

| Event type | Typical use |
| --- | --- |
| `session.started` | Session bootstrap, audit |
| `turn.started` / `turn.completed` | Per-turn resets, metrics |
| `message.completed` | Model output logging |
| `action.result` | Tool result inspection |
| `step.started` / `step.completed` / `step.failed` | Step-level tracing |
| `turn.failed` / `session.failed` / `session.completed` | Failure and completion handling |
| `session.waiting` | Parked-session notifications |

Handlers are observe-only. They cannot inject model context. To contribute runtime model messages, use `defineDynamic` and `defineInstructions` under `agent/instructions/`.

### `HookContext`

Every handler receives `(event, ctx)` where `ctx` is the last argument. `HookContext` extends `SessionContext` with agent and channel metadata:

```ts
interface HookContext {
  readonly agent: { readonly name: string; readonly nodeId?: string };
  readonly channel: { readonly kind?: string; readonly continuationToken?: string };
  readonly session: { readonly id: string; /* + auth, turn, parent */ };
  getSandbox(): Promise<SandboxSession>;
  getSkill(identifier: string): SkillHandle;
}
```

### Execution order

When a stream event fires:

```mermaid
sequenceDiagram
  participant Adapter as Channel adapter
  participant Stream as Durable stream
  participant Hooks as Hook registry
  participant Dynamic as Dynamic resolvers

  Adapter->>Stream: emit (adapter handler, then write)
  Stream->>Hooks: typed handlers for event.type
  Hooks->>Hooks: wildcard (*) handlers
  Hooks->>Dynamic: dynamic tool/skill/instruction resolvers
```

1. **Emit** — the channel adapter handler runs, then the event is written to the durable stream.
2. **Hooks** — typed handlers for the event type run first, then `*` wildcard handlers. Return values are ignored.
3. **Dynamic resolvers** — tool, skill, and instruction resolvers subscribed to the event type update the active capability set.

Hooks always run after durable recording, so a thrown handler does not roll back the stream.

### Failure behavior

A thrown hook handler propagates through the emit composer and surfaces as `turn.failed`. If a hook subscribed to a failure-cascade event also throws, the failure escalates to `session.failed`. Wrap hook bodies in `try`/`catch` when you need belt-and-suspenders semantics.

### Narrowing tool results

Import `toolResultFrom` from `eve/tools` to narrow `action.result` events to a specific authored tool or MCP connection:

```ts
import { toolResultFrom } from "eve/tools";
import getWeather from "../tools/get-weather";
import linear from "../connections/linear";

"action.result"(event) {
  const weather = toolResultFrom(event.data.result, getWeather);
  if (weather) console.log(weather.output.temperature);

  const linearResult = toolResultFrom(event.data.result, linear);
  if (linearResult) console.log(linearResult.connectionToolName, linearResult.output);
}
```

Returns `undefined` when the result does not match or when `isError` is `true`.

### Subagent hook isolation

Subagents may carry their own `agent/hooks/` directory. Subagent hooks fire only inside the subagent scope. Parent hooks do not fire for subagent turns, and subagent hooks see only the subagent's own context.

## Session context (`ctx`)

`ctx` is the shared runtime surface passed to tool `execute`, hook handlers, and channel event handlers. Tools with a declared `auth` strategy receive `ToolContext`, which extends `SessionContext` with token accessors.

### `ctx.session`

Read-only durable metadata for the active execution:

| Field | Type | Description |
| --- | --- | --- |
| `id` | `string` | Active session identifier |
| `turn.id` | `string` | Current turn identifier |
| `turn.sequence` | `number` | Turn sequence within the session |
| `auth.current` | `SessionAuthContext \| null` | Caller for the active inbound turn |
| `auth.initiator` | `SessionAuthContext \| null` | Caller that started the durable session |
| `parent` | `SessionParent \| undefined` | Present for child subagent sessions |

`SessionParent` includes `callId`, `sessionId`, `rootSessionId`, and `turn` (the immediate parent's turn metadata).

Behavior notes:

- Unprotected agents expose both `auth.current` and `auth.initiator` as `null`.
- Top-level schedule sessions expose the framework app principal (`principalId: "eve:app"`, `principalType: "runtime"`).
- `parent` is present only for subagent child sessions.

```ts title="agent/tools/who_called_me.ts"
async execute(_input, ctx) {
  return {
    sessionId: ctx.session.id,
    turnId: ctx.session.turn.id,
    currentCaller: ctx.session.auth.current?.principalId,
    initiator: ctx.session.auth.initiator?.principalId,
    parentSessionId: ctx.session.parent?.sessionId,
    parentCallId: ctx.session.parent?.callId,
  };
}
```

### `ctx.getSandbox()`

Returns a live `SandboxSession` handle for the current agent's sandbox.

- Async — Eve binds or restores sandbox state lazily.
- Takes no arguments; each agent has exactly one sandbox.
- Node-local visibility — a subagent sees its own sandbox, not the parent's.
- Throws when sandbox access is not attached to the active runtime path.

```ts
const sandbox = await ctx.getSandbox();
const result = await sandbox.run({ command: "npm test" });
```

`SandboxSession.resolvePath(path)` returns the backend-native path for a logical `/workspace/...` location.

### `ctx.getSkill(identifier)`

Returns a synchronous `SkillHandle` for a path-derived skill id visible to the current agent.

```ts
const skill = ctx.getSkill("research");
const notes = await skill.file("references/checklist.md").text();
```

- File content reads lazily from the active sandbox.
- Requires sandbox access on the active runtime path.
- A missing skill surfaces when a file accessor reads a missing sandbox path.
- The handle exposes `name` and `file(relativePath)`.

## Tool authorization: `getToken` and `requireAuth`

`getToken()` and `requireAuth()` exist only on `ToolContext` — the `ctx` passed to `defineTool(...).execute`. They are meaningful only when the tool declares an `auth` strategy.

```ts title="agent/tools/fetch-data.ts"
import { defineTool } from "eve/tools";
import { connect } from "@vercel/connect/eve";

export default defineTool({
  description: "Fetch data from an authenticated API.",
  inputSchema: z.object({ id: z.string() }),
  auth: connect("my-api"),
  async execute({ id }, ctx) {
    const { token } = await ctx.getToken();
    return fetch(`https://api.example.com/${id}`, {
      headers: { Authorization: `Bearer ${token}` },
    }).then((r) => r.json());
  },
});
```

<ParamField body="auth" type="ToolAuthDefinition">
Optional on `defineTool`. Accepts the same shapes as connection `auth`: a `getToken`-only object (static API keys, pre-provisioned JWTs; `principalType` defaults to `"app"`), or a full interactive OAuth definition (`connect("...")` from `@vercel/connect/eve`, or `defineInteractiveAuthorization`).
</ParamField>

<ResponseField name="getToken()" type="Promise<TokenResult>">
Resolves the bearer token for the tool's declared `auth`, consulting the per-step token cache before invoking the authored `getToken`. For interactive strategies, a cache miss throws `ConnectionAuthorizationRequiredError`; the runtime parks the turn on a framework-owned callback URL and re-runs the tool after OAuth completes.
</ResponseField>

<ResponseField name="requireAuth()" type="never">
Throws `ConnectionAuthorizationRequiredError` to gate execution on authorization without resolving a token first. The runtime converts the error into a consent prompt for interactive strategies.
</ResponseField>

<Warning>
Calling `ctx.getToken()` or `ctx.requireAuth()` on a tool without an `auth` field throws immediately.
</Warning>

The authorization machinery mirrors connection flows: per-step token caching, park/resume on interactive OAuth, loop-guard on tokens rejected immediately after sign-in, and token eviction on downstream 401s.

## Where managed-context APIs are valid

All `defineState` operations, `ctx` accessors, and ALS-backed context reads require an active harness step (`contextStorage.run`).

| Surface | `defineState` / `ctx` | Notes |
| --- | --- | --- |
| `defineTool(...).execute(input, ctx)` | Valid | `ToolContext` when `auth` is declared |
| `defineHook` event handlers | Valid | `HookContext` |
| Channel adapter event handlers | Valid | `SessionContext` |
| `defineDynamic` resolver callbacks | Valid | Framework builds `SessionContext` internally |
| Sandbox `onSession` hook | Valid | Receives `{ ctx, use }`; runs inside ALS |
| Async boundaries within the same authored callback chain | Valid | ALS propagates across `await` |
| Module top-level evaluation | **Throws** | No active context at import time |
| Build scripts / discovery-time code | **Throws** | Outside runtime execution |
| Schedule `run` handler | **No `ctx`** | Receives `ScheduleHandlerArgs` (`receive`, `waitUntil`, `appAuth`) |
| Sandbox `bootstrap` hook | **No `ctx`** | Receives `{ use }` only; runs at build/prewarm |
| Instrumentation `setup` | **No `ctx`** | Domain-specific arguments |

<Info>
Sandbox `onSession` is an exception to the "non-runtime callback" pattern: it receives `ctx: SessionContext` and executes inside the active Eve context, so `defineState` and `ctx.getSandbox()` work there. Sandbox `bootstrap` does not receive session context.
</Info>

Calling any managed API outside an active scope throws immediately:

```
No active Eve context. Call this function only from authored runtime code such as tools, steps, and model callbacks.
```

## Context lifecycle

```text
Step start
  ├─ Deserialize durable context (session id, auth, author state, bundle, …)
  ├─ Rebuild virtual/step-local values (session projection, sandbox access, skills)
  ├─ Enter ALS scope (contextStorage.run)
  │    └─ Authored code: ctx.*, defineState.get/update
  └─ Step end
       ├─ Serialize durable context → workflow checkpoint
       └─ Clear virtual context
```

Seed keys (`eve.sessionId`, `eve.auth`, author `defineState` slots, …) persist across step boundaries. Derived keys (`eve.session`, `eve.sandbox`) are rebuilt each step by context providers and are not serialized directly.

## Hook vs tool vs dynamic capability

| Need | Use |
| --- | --- |
| Observe runtime events (audit, metrics, alerting) | `defineHook` `events.<type>` |
| Provide structured input to the model on demand | `defineTool` |
| Change model context based on stream events | `defineDynamic` under `agent/instructions/` |
| Subscribe to platform-specific ingress | Channel adapter handler |
| Durable per-session working memory | `defineState` |

Stream-event hooks and channel adapter event handlers are structurally similar. Choose the adapter handler for platform-specific behavior; choose `events.*` for agent-level behavior that fires across every channel.

## Related pages

<CardGroup>
  <Card title="Execution model and durability" href="/execution-model">
    Session/turn/step nesting, workflow checkpoints, and crash-resume semantics that make defineState durable.
  </Card>
  <Card title="Sessions and streaming" href="/sessions-and-streaming">
    NDJSON stream event vocabulary that defineHook subscribers observe.
  </Card>
  <Card title="Tools" href="/tools">
    defineTool execute(ctx), auth strategies, and requireAuth flows.
  </Card>
  <Card title="Context control" href="/context-control">
    defineDynamic resolvers that run after hooks on the same stream events.
  </Card>
  <Card title="Subagents and schedules" href="/subagents-and-schedules">
    Subagent state and hook isolation; schedule run handlers without ctx.
  </Card>
  <Card title="TypeScript API reference" href="/typescript-api">
    Full define* import map and exported context types.
  </Card>
</CardGroup>

---

## 19. Instrumentation and evals

> defineInstrumentation OTel setup, workflow run tags, defineEval/defineEvalConfig authoring under evals/, eve eval flags, judges, assertions, and reporters (Braintrust, JUnit).

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/19-instrumentation-and-evals.md
- Generated: 2026-06-16T19:29:03.915Z

### Source Files

- `docs/guides/instrumentation.md`
- `docs/evals/overview.mdx`
- `packages/eve/src/public/definitions/instrumentation.ts`
- `packages/eve/src/evals/define-eval.ts`
- `packages/eve/src/evals/cli/eval.ts`
- `packages/eve/src/compiler/channel-instrumentation-types.ts`

---
title: "Instrumentation and evals"
description: "defineInstrumentation OTel setup, workflow run tags, defineEval/defineEvalConfig authoring under evals/, eve eval flags, judges, assertions, and reporters (Braintrust, JUnit)."
---

Eve exposes two complementary quality surfaces: **instrumentation** for runtime observability (`agent/instrumentation.ts`, OpenTelemetry spans, and framework-owned workflow run tags) and **evals** for repeatable scored checks (`evals/*.eval.ts`, `evals/evals.config.ts`, and the `eve eval` CLI). Instrumentation answers what happened during production sessions; evals drive the same HTTP session protocol your users hit and grade the result before you ship a change.

## Observability surfaces

Eve observes an agent through three distinct surfaces. They write to different backends and are configured differently:

| Surface | Configured in `instrumentation.ts`? | What it records |
| --- | --- | --- |
| Workflow run tags (`$eve.*`) | No — automatic | Framework-owned attributes on each Vercel Workflow run. Dashboards stitch session, turn, and subagent runs into a tree and surface model and token usage. |
| OpenTelemetry export | Yes — `setup`, `recordInputs`, `recordOutputs`, `functionId` | Where AI SDK spans are exported and what they record. |
| Runtime context events | Yes — `events["step.started"]` | Per-model-call values merged into AI SDK telemetry spans. |

Workflow run tags are queryable in the Vercel Workflow dashboard (and the **Agent Runs** observability tab on Vercel deployments). OpenTelemetry and runtime context both ride on AI SDK spans and export to any OTel-compatible backend you configure.

```mermaid
flowchart TB
  subgraph authored ["Authored (agent/instrumentation.ts)"]
    setup["setup() → registerOTel"]
    events["events['step.started'] → runtimeContext"]
    record["recordInputs / recordOutputs / functionId"]
  end

  subgraph automatic ["Framework-owned"]
    tags["$eve.* workflow run tags"]
    spans["ai.eve.turn parent span + AI SDK children"]
    ctx["eve.session.id, eve.turn.id, eve.channel.kind, …"]
  end

  subgraph sinks ["Sinks"]
    otel["OTel backend (Braintrust, Datadog, Jaeger, …)"]
    workflow["Vercel Workflow / Agent Runs dashboard"]
  end

  setup --> otel
  events --> spans
  record --> spans
  ctx --> spans
  spans --> otel
  tags --> workflow
```

## `defineInstrumentation`

Eve auto-discovers `agent/instrumentation.ts` and runs it at server startup before any agent code. Export the result of `defineInstrumentation` as the default export. **The file's presence implicitly enables telemetry** — there is no separate `isEnabled` toggle.

<CodeGroup>
```ts title="agent/instrumentation.ts — OTel setup"
import { BraintrustExporter } from "@braintrust/otel";
import { defineInstrumentation } from "eve/instrumentation";
import { registerOTel } from "@vercel/otel";

export default defineInstrumentation({
  setup: ({ agentName }) =>
    registerOTel({
      serviceName: agentName,
      traceExporter: new BraintrustExporter({
        parent: `project_name:${agentName}`,
        filterAISpans: true,
      }),
    }),
});
```

```ts title="agent/instrumentation.ts — runtime context"
import { defineInstrumentation, isChannel } from "eve/instrumentation";
import supportChannel from "./channels/support.js";

export default defineInstrumentation({
  events: {
    "step.started"(input) {
      if (!isChannel(input.channel, supportChannel)) return undefined;
      return {
        runtimeContext: {
          "support.channel_id": input.channel.metadata.channelId ?? "",
          "support.user_id": input.channel.metadata.triggeringUserId ?? "",
        },
      };
    },
  },
});
```
</CodeGroup>

### Fields

<ParamField body="setup" type="(context) => void">
Invoked at server startup with `{ agentName }`. Use it to call `registerOTel` or another OTel provider setup. `agentName` is resolved at compile time from the package `name` (falling back to the app directory name).
</ParamField>

<ParamField body="recordInputs" type="boolean" default="true">
Records full message history on each step span. Set `false` for sensitive inputs or to reduce span payload size.
</ParamField>

<ParamField body="recordOutputs" type="boolean" default="true">
Records model outputs on spans. Set `false` to disable output recording.
</ParamField>

<ParamField body="functionId" type="string">
Overrides `ai.telemetry.functionId` on spans. Defaults to the agent name.
</ParamField>

<ParamField body="events['step.started']" type="(input) => { runtimeContext } | undefined">
Runs once per model-call attempt after Eve assembles the final model input. Returned `runtimeContext` values merge onto AI SDK spans; child spans inherit them. Keys beginning with `eve.` are reserved and dropped. Return `undefined` to contribute nothing.
</ParamField>

The `step.started` callback receives `session` (id, auth, parent lineage), `turn` (id and sequence), `step` (zero-based index), `channel` (`kind` and channel-owned `metadata`), and `modelInput` (resolved instructions and messages). Narrow authored channels with `isChannel(input.channel, myChannel)` or by checking `input.channel.kind === "channel:<filename>"`. Framework channels use `http`, `schedule`, or `subagent`.

### Trace hierarchy

When telemetry is enabled, each turn produces a span tree:

```text
ai.eve.turn  {eve.session.id, eve.turn.id, …}
  +-- ai.streamText                           step 1
  |     +-- ai.streamText.doStream            model call
  |     +-- ai.toolCall  {toolName: search}   tool exec
  +-- ai.streamText                           step 2
        +-- ai.streamText.doStream
        +-- ai.toolCall  {toolName: read}
```

Eve creates the `ai.eve.turn` parent span per turn and injects framework runtime context (`eve.version`, `eve.session.id`, `eve.environment`, `eve.turn.id`, `eve.turn.sequence`, `eve.step.index`, `eve.channel.kind`) alongside any values your `step.started` callback returns.

<Note>
Any OTel-compatible backend works. Install the exporter package you need and configure it in `setup`. The Braintrust example above is one option — Honeycomb, Datadog, and Jaeger follow the same pattern.
</Note>

## Workflow run tags

Separately from OpenTelemetry, Eve tags every workflow run with reserved `$eve.*` attributes. Authored code cannot set or override the `$eve.` namespace. Tags are emitted automatically on every session, turn, and subagent run, whether or not `instrumentation.ts` is present. Tag writes are best-effort: a failure is logged once per process and swallowed so a broken tag emit never breaks the agent.

### Structural tags

| Tag | Values / meaning |
| --- | --- |
| `$eve.type` | `"session"`, `"turn"`, or `"subagent"` |
| `$eve.parent` | Session id of the immediate parent |
| `$eve.root` | Session id of the root session (group a tree with `$eve.root=<id>`) |
| `$eve.parent_call` | Parent runtime-action tool call id (subagent rows only) |
| `$eve.parent_turn` | Parent turn id that dispatched the subagent (subagent rows only) |
| `$eve.subagent` | Compiled graph node id (subagent runs only) |
| `$eve.trigger` | Channel kind that started the run |
| `$eve.title` | Truncated title from the first user message |

### Per-turn usage tags

Written on each step of a turn, accumulating cumulative totals (last write wins):

| Tag | Meaning |
| --- | --- |
| `$eve.model` | Model id for the turn |
| `$eve.input_tokens` | Running input token count |
| `$eve.output_tokens` | Running output token count |
| `$eve.cache_read_tokens` | Running cache-read token count |
| `$eve.cache_write_tokens` | Running cache-write token count |
| `$eve.tool_count` | Number of tools available to the turn |

On Vercel, the platform auto-detects `eve` and surfaces an **Agent Runs** view under **Observability**. That view is separate from OTel export — use OTel when you want spans in a third-party backend.

## Eval authoring

Evals live under the app-root `evals/` directory. Eve discovers `*.eval.ts` files recursively and requires exactly one `evals/evals.config.ts` at the root of `evals/`.

:::files
my-agent/
├── agent/
├── evals/
│   ├── evals.config.ts          # required run-wide defaults
│   ├── smoke.eval.ts
│   └── weather/
│       ├── brooklyn-forecast.eval.ts
│       └── no-tools-for-greetings.eval.ts
└── package.json
:::

Eval identity is **path-derived** — do not author `id` or `name`. `evals/weather/brooklyn-forecast.eval.ts` becomes id `weather/brooklyn-forecast`. Directories group related evals; shared helpers go in sibling non-eval files (any name that does not end in `.eval.ts`).

### `defineEval`

Each eval file default-exports one `defineEval({ ... })` call (or an array of them for dataset fan-out). The only required field is `test`:

```ts title="evals/weather/brooklyn-forecast.eval.ts"
import { defineEval } from "eve/evals";
import { includes } from "eve/evals/expect";

export default defineEval({
  description: "Basic message and tool-usage coverage for the weather agent.",
  async test(t) {
    await t.send("What is the weather in Brooklyn?");
    t.completed();
    t.calledTool("get_weather");
    t.check(t.reply, includes("Sunny"));
  },
});
```

Optional per-eval fields:

| Field | Purpose |
| --- | --- |
| `description` | Human-readable summary (shown in `--list` output) |
| `judge` | Override judge model for `t.judge.*` on this eval |
| `tags` | Filter with `eve eval --tag` |
| `metadata` | Passed through to reporters |
| `timeoutMs` | Per-eval timeout (overridden by CLI `--timeout`) |
| `reporters` | Reporters scoped to this eval only |

`defineEval` rejects legacy keys (`input`, `run`, `checks`, `scores`, `expected`, `thresholds`, `parseOutput`, `model`, `requires`, `cases`). Drive the agent inside `test` and assert inline.

### `defineEvalConfig`

```ts title="evals/evals.config.ts"
import { defineEvalConfig } from "eve/evals";
import { Braintrust } from "eve/evals/reporters";

export default defineEvalConfig({
  judge: { model: "openai/gpt-5.4-mini" },
  reporters: [Braintrust({ projectName: "my-agent" })],
  maxConcurrency: 4,
  timeoutMs: 120_000,
});
```

All fields are optional — an empty `defineEvalConfig({})` satisfies the requirement. When set:

| Field | Default | Precedence |
| --- | --- | --- |
| `judge.model` | none | Per-call `model` → per-eval `judge` → config `judge` |
| `reporters` | none | Suppressed by `--skip-report` |
| `maxConcurrency` | `8` | CLI `--max-concurrency` overrides |
| `timeoutMs` | none | Per-eval `timeoutMs` → CLI `--timeout` |

Array-exported eval files derive ids as `<file-id>/<zero-padded-index>` (e.g. `weather/0000`, `weather/0001`).

## The `t` context

`t` is both the session driver and the assertion surface passed to `test(t)`. There are no separate `input`, `run`, `checks`, or `scores` fields.

**Drive** the agent:

| Method | Purpose |
| --- | --- |
| `t.send(input)` | Send one turn; resolves when the turn settles |
| `t.respond(...)` / `t.respondAll(optionId)` | Resolve HITL input requests and resume |
| `t.sendFile(text, path, mediaType?)` | Send text with a local file attached |
| `t.expectInputRequests(filter?)` | Assert the run parked on HITL input |
| `t.newSession()` | Start an additional independent session |
| `t.reply` | Last assistant message on the primary session |
| `t.events` / `t.sessionId` / `t.state` | Stream events, session id, resumable state |
| `t.log(message)` | Structured log (visible with `--verbose`) |

Evals exercise the same HTTP session protocol as production clients. The runner boots a local dev server or targets a remote deployment via `--url`.

## Assertions

Assertions record results during `test(t)` and are evaluated when the run completes. Every assertion returns a chainable handle; severity rides on the assertion itself — there is no separate thresholds map.

### Three assertion surfaces

| Surface | Examples | Default severity |
| --- | --- | --- |
| Run-level methods | `t.completed()`, `t.calledTool("get_weather")`, `t.usedNoTools()`, `t.toolOrder([...])` | gate |
| `t.check(value, assertion)` | `t.check(t.reply, includes("sunny"))` with builders from `eve/evals/expect` | depends on builder |
| `t.judge.autoevals.*` | `t.judge.autoevals.closedQA("cites a source")` | soft |

**Run-level methods** observe the whole run:

| Method | Asserts |
| --- | --- |
| `t.completed()` | Run did not fail and did not park on unanswered HITL input |
| `t.didNotFail()` | No terminal failure (parked runs pass) |
| `t.waiting()` | Run parked on HITL input |
| `t.messageIncludes(token)` | Joined assistant text contains token |
| `t.outputEquals(value)` / `t.outputMatches(schema)` | Structured output equality or Standard Schema validation |
| `t.calledTool(name, opts?)` | Matching tool call (`input`, `output`, `isError`, `times`) |
| `t.notCalledTool(name)` | No call to `name` |
| `t.toolOrder([...names])` | Tool names appear in order |
| `t.usedNoTools()` | No tool calls |
| `t.maxToolCalls(n)` | At most `n` tool calls |
| `t.calledSubagent(name, opts?)` | Subagent delegation |
| `t.noFailedActions()` | No tool, subagent, or skill action reported failure |
| `t.event(predicate, label)` | Escape hatch over the typed event stream |

**Value builders** from `eve/evals/expect`:

| Builder | Scores | Default |
| --- | --- | --- |
| `includes(substring)` | substring match | gate |
| `equals(value)` | deep structural equality | gate |
| `matches(schema)` | Standard Schema validation | gate |
| `similarity(expected)` | normalized Levenshtein, 0–1 | soft |

### Gate vs soft

| Severity | Behavior |
| --- | --- |
| **Gate** | Failed gate → eval `failed` → `eve eval` exits non-zero |
| **Soft** (no threshold) | Tracked in reports; never fails |
| **Soft** with `.atLeast(n)` | Below threshold → eval `scored`; fatal under `--strict` |

Override per assertion: `.gate(threshold?)`, `.soft(threshold?)`, `.atLeast(threshold)`.

```ts
t.completed();                                              // gate
t.calledTool("get_weather").soft();                         // tracked metric
t.check(t.reply, similarity("Sunny")).atLeast(0.8);         // soft bar
t.judge.autoevals.closedQA("cites a source").gate(0.7);     // hard judge gate
```

<Warning>
`t.calledTool` and `t.usedNoTools` are mutually exclusive in the same run. `t.completed()` subsumes `t.didNotFail()` — use `didNotFail` only when you intentionally allow a parked run.
</Warning>

## LLM-as-judge

When deterministic assertions cannot capture "good enough," grade with `t.judge.autoevals.*`. The judge model is **never** the agent under test — Eve uses it only for scoring.

| Grader | Grades |
| --- | --- |
| `t.judge.autoevals.factuality(expected)` | Factual consistency against an expected answer |
| `t.judge.autoevals.summarizes(expected)` | Summary quality against expected text |
| `t.judge.autoevals.closedQA(criteria)` | Free-form yes/no criterion |
| `t.judge.autoevals.sql(expected)` | Semantic SQL equivalence |

Judge model resolution (innermost wins):

1. Per-call: `t.judge.autoevals.closedQA("…", { model, modelOptions })`
2. Per-eval: `defineEval({ judge: { model, modelOptions }, test })`
3. Project default: `defineEvalConfig({ judge: { model, modelOptions } })`

A string model id (e.g. `"openai/gpt-5.4-mini"`) routes through the Vercel AI Gateway and needs gateway credentials in the environment. An AI SDK `LanguageModel` instance runs directly. Calling `t.judge.*` with no judge model resolved records a failed gate. With a model configured but no credentials, judge-backed evals **skip visibly** rather than failing spuriously.

## `eve eval`

<Steps>
<Step title="Author evals">
Create `evals/evals.config.ts` and one or more `evals/**/*.eval.ts` files with `defineEval`.
</Step>
<Step title="Run locally">
```bash
eve eval                       # all discovered evals against a local dev server
eve eval weather               # one eval or every eval under evals/weather/
eve eval --tag fast            # only evals carrying a tag
```
</Step>
<Step title="Target remote or tune CI">
```bash
eve eval --url https://<app>    # deployed instance
eve eval --strict --junit .eve/junit.xml   # CI: soft thresholds fail too
```
</Step>
</Steps>

### CLI flags

<ParamField body="[evalIds...]" type="string[]">
Eval ids or directory prefixes. Omit to run all discovered evals. `weather` matches `evals/weather.eval.ts`, everything under `evals/weather/`, and array entries from `evals/weather.eval.ts`.
</ParamField>

<ParamField body="--url" type="string">
Remote agent URL. Skips local dev server startup.
</ParamField>

<ParamField body="--tag" type="string[]">
Run only evals carrying any requested tag.
</ParamField>

<ParamField body="--strict" type="boolean">
Fail exit code when any soft assertion falls below its threshold.
</ParamField>

<ParamField body="--list" type="boolean">
Print discovered evals without running them. Use `--json` for machine-readable output.
</ParamField>

<ParamField body="--timeout" type="integer">
Per-eval timeout in milliseconds. Non-negative integer.
</ParamField>

<ParamField body="--max-concurrency" type="integer">
Max concurrent eval executions. Positive integer; default `8`.
</ParamField>

<ParamField body="--json" type="boolean">
Output run summary as JSON. Suppresses the console reporter.
</ParamField>

<ParamField body="--junit" type="path">
Write JUnit XML to the given path.
</ParamField>

<ParamField body="--skip-report" type="boolean">
Skip config-level and eval-defined reporters (e.g. Braintrust).
</ParamField>

<ParamField body="--verbose" type="boolean">
Stream per-eval `t.log` lines to stdout.
</ParamField>

### Exit codes and verdicts

| Exit code | Meaning |
| --- | --- |
| `0` | Every eval passed its gates (and soft thresholds under `--strict`) |
| `1` | Any eval failed — gate miss, execution error, or strict threshold miss |
| `2` | Configuration error — no evals discovered, no tag matches, invalid flags, missing `evals.config.ts` |

Per-eval verdicts:

| Verdict | Meaning |
| --- | --- |
| `passed` | No execution error; every gate held; every soft threshold met |
| `failed` | Gate assertion failed or execution errored (timeout, transport, thrown task) |
| `scored` | Every gate held but a soft assertion fell below its threshold |

### Artifacts

Each run writes artifacts under `.eve/evals/<timestamp>/`:

| File | Contents |
| --- | --- |
| `summary.json` | Aggregated run outcome |
| `results.jsonl` | One line per eval result |
| `evals/<id>.json` | Per-eval assertions, verdict, logs |
| `evals/<id>.events.ndjson` | Captured NDJSON event stream |

The console summary stays tight; when an eval fails, the artifact directory has the full event stream and assertion details.

## Reporters

Eve runs and grades everything itself. Reporters ship results to external destinations.

### Braintrust

```ts title="evals/evals.config.ts"
import { defineEvalConfig } from "eve/evals";
import { Braintrust } from "eve/evals/reporters";

export default defineEvalConfig({
  reporters: [Braintrust({ projectName: "weather-agent" })],
});
```

`Braintrust({ projectName?, projectId?, experimentName?, baseExperimentName?, baseExperimentId?, update? })` uploads to Braintrust experiments. Gate assertions log as binary scores under a `gate:` prefix. Requires the `braintrust` package and `BRAINTRUST_API_KEY`. Use `--skip-report` to suppress during local iteration.

### JUnit

```bash
eve eval --strict --junit .eve/junit.xml
```

`JUnit({ filePath })` writes one `<testcase>` per eval (named by path-derived id). Failed gates and execution errors appear as failure messages. The `--junit` flag is usually preferable in CI because the output path is owned by the pipeline, not the eval file.

### Custom reporters

Implement `EvalReporter` from `eve/evals/reporters`:

```ts
interface EvalReporter {
  onRunStart(evaluations, target): void | Promise<void>;
  onEvalComplete(result): void | Promise<void>;
  onRunComplete(summary): void | Promise<void>;
}
```

Attach reporters in `evals.config.ts` (observes every eval) or on individual eval definitions (scoped). The same reporter instance is not double-reported if listed in both places.

## Troubleshooting

| Symptom | Likely cause |
| --- | --- |
| No spans in OTel backend | Missing `agent/instrumentation.ts` or `setup` not calling `registerOTel` |
| `eve eval` exits `2` with config error | Missing `evals/evals.config.ts` — create `defineEvalConfig({})` at minimum |
| Judge assertions always fail | No `judge.model` resolved at any level, or missing gateway credentials |
| Eval passes locally, fails in CI | Run `eve eval --strict`; check model-provider credentials in the CI environment |
| Duplicate eval id error | Array-exported file collides with a nested eval path (e.g. `weather.eval.ts` and `weather/0000.eval.ts`) |

For discovery diagnostics on the agent side, run `eve info` and inspect `.eve/discovery/diagnostics.json`.

## Related pages

<CardGroup>
<Card title="Project layout" href="/project-layout">
Where `evals/` and `agent/instrumentation.ts` fit in the authored tree and how path-derived naming works.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full `eve eval` command reference, exit codes, and the edit-info-dev-build-start loop.
</Card>
<Card title="Sessions and streaming" href="/sessions-and-streaming">
The HTTP session protocol evals drive and the NDJSON events assertions read.
</Card>
<Card title="Execution model" href="/execution-model">
Session/turn/step nesting, workflow durability, and the run tree behind `$eve.*` tags.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Model credentials for evals and the Vercel Agent Runs observability surface.
</Card>
<Card title="TypeScript API" href="/typescript-api">
`defineInstrumentation`, `defineEval`, `defineEvalConfig`, and eval entrypoint import paths.
</Card>
</CardGroup>

---

## 20. Client integration

> eve/client Client and ClientSession, auth policies, streaming reducers, useEveAgent React/Vue/Svelte hooks, and framework plugins (eve/next, eve/nuxt, eve/sveltekit).

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/20-client-integration.md
- Generated: 2026-06-16T19:29:36.748Z

### Source Files

- `docs/guides/client/overview.mdx`
- `docs/guides/client/streaming.mdx`
- `docs/guides/frontend/overview.mdx`
- `packages/eve/src/client/index.ts`
- `packages/eve/src/client/client.ts`
- `packages/eve/src/react/use-eve-agent.ts`

---
title: "Client integration"
description: "eve/client Client and ClientSession, auth policies, streaming reducers, useEveAgent React/Vue/Svelte hooks, and framework plugins (eve/next, eve/nuxt, eve/sveltekit)."
---

The `eve/client` entrypoint is the typed HTTP client for `/eve/v1/*` routes. It wraps session POSTs, NDJSON stream consumption, reconnection, and cursor advancement. Browser chat UIs add `useEveAgent` from `eve/react`, `eve/vue`, or `eve/svelte` on top of the shared `EveAgentStore`, and framework plugins (`eve/next`, `eve/nuxt`, `eve/sveltekit`) mount same-origin Eve routes so the hooks default to `host: ""`.

## Integration layers

```text
┌─────────────────────────────────────────────────────────────┐
│  Browser UI (React / Vue / Svelte)                          │
│  useEveAgent → EveAgentStore → reducer → render-ready data  │
└──────────────────────────┬──────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────┐
│  eve/client: Client → ClientSession → MessageResponse         │
│  POST turn + NDJSON stream + reconnect + SessionState cursor  │
└──────────────────────────┬──────────────────────────────────┘
                           │ fetch
┌──────────────────────────▼──────────────────────────────────┐
│  /eve/v1/session, /eve/v1/session/:id, /eve/v1/health, …      │
│  (proxied same-origin by framework plugin in dev/deploy)      │
└───────────────────────────────────────────────────────────────┘
```

| Layer | Import | Responsibility |
| --- | --- | --- |
| TypeScript client | `eve/client` | Scripts, tests, evals, custom UIs without framework state |
| Framework hooks | `eve/react`, `eve/vue`, `eve/svelte` | Session lifecycle, optimistic projection, reactive `data` |
| Route mounting | `eve/next`, `eve/nuxt`, `eve/sveltekit` | Dev proxy / production rewrite to Eve service on same origin |

<Note>
Framework hooks and the raw client share `ClientAuth`, `HeadersValue`, `SendTurnPayload`, `SessionState`, and stream event types exported from `eve/client`.
</Note>

## Client and ClientSession

### Client

`Client` binds one `host`, auth policy, header policy, and per-turn reconnection budget. One client can own many concurrent `ClientSession` handles.

```ts
import { Client } from "eve/client";

const client = new Client({
  host: "http://127.0.0.1:3000",
});
```

<ParamField body="host" type="string" required>
Origin where Eve routes are mounted. Use `""` for same-origin browser calls against `/eve/v1/*`.
</ParamField>

<ParamField body="auth" type="ClientAuth">
Bearer or Basic credentials. Values can be static strings or async functions resolved before every HTTP call, including stream reconnects.
</ParamField>

<ParamField body="headers" type="HeadersValue">
Custom headers on every request. Static map or per-request resolver (for example bypass tokens).
</ParamField>

<ParamField body="maxReconnectAttempts" type="number" default="3">
Maximum stream reconnection attempts per turn after transient disconnects.
</ParamField>

<ParamField body="preserveCompletedSessions" type="boolean" default="false">
When `true`, keeps `continuationToken` and `sessionId` after `session.completed` so the next `send()` continues the same durable conversation instead of starting fresh.
</ParamField>

Utility methods:

| Method | Route | Purpose |
| --- | --- | --- |
| `health()` | `GET /eve/v1/health` | Fail fast before creating a session |
| `info()` | `GET /eve/v1/info` | Agent inspection payload |
| `fetch(path, init)` | Any path on `host` | Authenticated escape hatch for framework-owned routes |
| `session(state?)` | — | Create or resume a `ClientSession` |

Non-2xx responses throw `ClientError` with `status` and `body`.

### ClientSession

`ClientSession` tracks one conversation's `continuationToken`, `sessionId`, and `streamIndex`. Read `session.state` after each turn and serialize it to resume later.

```ts
const session = client.session();

// Resume from persisted cursor
const resumed = client.session({
  continuationToken: "eve:…",
  sessionId: "wrun_…",
  streamIndex: 10,
});

// Shorthand: continuation token only
const fromToken = client.session("eve:…");
```

`send()` posts to `POST /eve/v1/session` on the first turn, then `POST /eve/v1/session/:sessionId` for follow-ups. It returns a `MessageResponse` as soon as the POST completes.

<ParamField body="message" type="string | UserContent">
User turn text or multi-part content (text, file attachments as data URLs).
</ParamField>

<ParamField body="inputResponses" type="InputResponse[]">
HITL responses for pending approvals or `ask_question` prompts.
</ParamField>

<ParamField body="clientContext" type="string | string[] | JsonObject">
Ephemeral page context for the next model call only. Never persisted to durable session history.
</ParamField>

<ParamField body="outputSchema" type="StandardJSONSchemaV1 | JsonObject">
Structured output schema; the client lowers Standard Schema implementations to JSON Schema before send.
</ParamField>

<ParamField body="signal" type="AbortSignal">
Cancels the POST and stream. Arm timeouts before `await send()` so they cover both phases.
</ParamField>

<ParamField body="headers" type="Record<string, string>">
Per-turn headers merged on top of client-level headers.
</ParamField>

HITL delivery retries: when `inputResponses` is non-empty, the client retries POST delivery up to 10 times on `500` responses whose body matches `target session was not found`.

`stream(options?)` attaches to the existing NDJSON stream for the current `sessionId`, resuming from `streamIndex` unless `startIndex` overrides it. Throws if no message has been sent yet.

## Authentication

Client auth is transport-level. Route auth is enforced server-side on the Eve channel (`agent/channels/eve.ts`). Both must align for production browser integrations.

### Client-side credentials

<CodeGroup>
```ts Bearer
const client = new Client({
  host: "https://agent.example.com",
  auth: {
    bearer: async () => await getAccessToken(),
  },
});
```

```ts Basic
const client = new Client({
  host: "https://agent.example.com",
  auth: {
    basic: {
      username: "agent-client",
      password: async () => await getRotatingSecret(),
    },
  },
});
```

```ts Custom headers
const client = new Client({
  host: "https://agent.example.com",
  headers: async () => ({
    "x-vercel-protection-bypass": await getBypassToken(),
  }),
});
```
</CodeGroup>

Bearer tokens that resolve to an empty string omit the `Authorization` header entirely rather than sending `Bearer `.

### Server-side route auth

The default Eve channel is fail-closed: without an authored `agent/channels/eve.ts`, Eve registers `eveChannel({ auth: [localDev(), vercelOidc()] })`. Same-origin framework integrations inherit session cookies automatically; for token schemes, pass matching `auth` or `headers` to `Client` or `useEveAgent`.

<Warning>
Client credentials do not bypass server route auth. A `401` from `/eve/v1/session` means the channel rejected the request, not that the client misconfigured `host`.
</Warning>

## Streaming and MessageResponse

Every `ClientSession.send()` yields a `MessageResponse` that is both metadata (immediate `sessionId`, `continuationToken`) and an async iterable of NDJSON events.

### Aggregate a turn

```ts
const response = await session.send("Summarize the forecast.");
const result = await response.result();

console.log(result.status);   // "waiting" | "completed" | "failed"
console.log(result.message);  // final assistant text
console.log(result.events);   // all events this turn
```

`result()` consumes until a turn boundary: `session.waiting`, `session.completed`, or `session.failed`.

### Stream events live

```ts
const response = await session.send("Draft a plan.");

for await (const event of response) {
  if (event.type === "message.appended") {
    process.stdout.write(event.data.messageDelta);
  }
}
```

Each `MessageResponse` can only be consumed once. After abort, start a new `send()` for the next turn.

### Common stream events

| Event | UI use |
| --- | --- |
| `message.received` | Confirm user message landed |
| `reasoning.appended` | Render reasoning deltas |
| `message.appended` | Render assistant text deltas |
| `actions.requested` | Show tool calls |
| `action.result` | Show tool results |
| `input.requested` | Pause for HITL approval or question |
| `result.completed` | Structured output when `outputSchema` was set |
| `session.waiting` | Enable composer for next turn |
| `session.completed` | Terminal conversation |
| `session.failed` | Terminal failure |

Import helpers from `eve/client`:

```ts
import { isCurrentTurnBoundaryEvent, isTurnFailureEvent } from "eve/client";
import type { HandleMessageStreamEvent } from "eve/client";
```

### Reconnection

On transient socket disconnect, the client resumes from the number of events already consumed in the current turn, up to `maxReconnectAttempts` (default `3`). Caller-initiated `AbortSignal` aborts without reconnecting.

### Session cursor advancement

After each streamed turn, `advanceSession` updates `SessionState`:

- `session.waiting` → preserve `sessionId`, `continuationToken`, advance `streamIndex`
- `session.completed` or `session.failed` → reset to fresh state unless `preserveCompletedSessions: true` on `session.completed`

## Reducers and EveAgentStore

`EveAgentStore` is the framework-agnostic state machine behind all `useEveAgent` bindings. It drives one turn at a time (`send` rejects while `submitted` or `streaming`), manages optimistic projection, and notifies subscribers.

### Reducer contract

```ts
import type { EveAgentReducer } from "eve/client";

interface EveAgentReducer<TData> {
  initial(): TData;
  reduce(data: TData, event: EveAgentReducerEvent): TData;
}
```

`EveAgentReducerEvent` is either an authoritative Eve stream event or a client projection event:

| Client projection event | When emitted |
| --- | --- |
| `client.message.submitted` | Optimistic user message before `message.received` |
| `client.message.failed` | Submitted message failed before confirmation |
| `client.input.responded` | HITL responses sent via `send()` |

`events` on the store snapshot is the raw server stream only. Projection events feed `data` through the reducer but never appear in `events`.

### Default message reducer

`defaultMessageReducer()` projects into `{ messages: EveMessage[] }` following the AI SDK `UIMessage` `messages[].parts[]` convention. Parts include user text, assistant text, reasoning, tool calls, tool results, and `dynamic-tool` parts with HITL metadata at `part.toolMetadata?.eve?.inputRequest`.

Custom reducers receive the same event union:

```tsx
import { useEveAgent } from "eve/react";
import type { EveAgentReducer } from "eve/react";

const toolCounter: EveAgentReducer<{ toolCalls: number }> = {
  initial: () => ({ toolCalls: 0 }),
  reduce: (data, event) =>
    event.type === "actions.requested" ? { toolCalls: data.toolCalls + 1 } : data,
};

const agent = useEveAgent({ reducer: toolCounter });
```

## useEveAgent hooks

All three framework bindings wrap `EveAgentStore` with framework-specific reactivity. Session-shaping options (`host`, `reducer`, `session`, `initialSession`, `auth`, `headers`, `maxReconnectAttempts`, `optimistic`) are read once at store creation; remount to change them. Lifecycle callbacks refresh every render.

| Framework | Import | Return shape |
| --- | --- | --- |
| React | `eve/react` | Snapshot object + `send`, `stop`, `reset` via `useSyncExternalStore` |
| Vue | `eve/vue` | Reactive `ComputedRef`s + commands; auto-imported by `eve/nuxt` |
| Svelte | `eve/svelte` | Rune-friendly getters on a reactive binding |

### Returned state

| Field | Type | Purpose |
| --- | --- | --- |
| `data` | `TData` | Reducer projection (default: `{ messages }`) |
| `status` | `"ready" \| "submitted" \| "streaming" \| "error"` | Composer gating |
| `error` | `Error \| undefined` | Last failure |
| `events` | `HandleMessageStreamEvent[]` | Authoritative server stream |
| `session` | `SessionState` | Serializable cursor |
| `send` | `(SendTurnPayload) => Promise<void>` | Dispatch turn |
| `stop` | `() => void` | Abort in-flight turn |
| `reset` | `() => void` | Clear local state, new session |

### Basic React chat

```tsx
"use client";

import { useEveAgent } from "eve/react";

export function Chat() {
  const agent = useEveAgent();
  const isBusy = agent.status === "submitted" || agent.status === "streaming";

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const form = new FormData(event.currentTarget);
        const message = String(form.get("message") ?? "").trim();
        if (message.length > 0) void agent.send({ message });
      }}
    >
      {agent.data.messages.map((message) => (
        <article key={message.id}>
          <header>{message.role}</header>
          {message.parts.map((part, index) =>
            part.type === "text" ? <p key={index}>{part.text}</p> : null,
          )}
        </article>
      ))}
      <input name="message" disabled={isBusy} />
      <button disabled={isBusy} type="submit">Send</button>
    </form>
  );
}
```

### Sending turns

```tsx
await agent.send({ message: "Summarize this session." });

await agent.send({
  message: [
    { type: "text", text: "What is in this file?" },
    { type: "file", data: fileDataUrl, mediaType: "application/pdf", filename: "report.pdf" },
  ],
});
```

Use `createDataUrlFilePart` and `createTextWithFileContent` from `eve/client` to build file parts from bytes.

### Human-in-the-loop

When the stream emits `input.requested`, read the pending request from the latest message's `dynamic-tool` part:

```tsx
const request = agent.data.messages
  .at(-1)
  ?.parts.find((part) => part.type === "dynamic-tool" && part.toolMetadata?.eve?.inputRequest)
  ?.toolMetadata?.eve?.inputRequest;

if (request) {
  await agent.send({
    inputResponses: [{ requestId: request.requestId, optionId: "approve" }],
  });
}
```

### Client context per turn

`clientContext` adds ephemeral context for one model call. Pass it on `send()` or attach globally via `prepareSend`:

```tsx
const agent = useEveAgent({
  prepareSend: (input) => ({
    ...input,
    clientContext: { route: location.pathname },
  }),
});
```

### Lifecycle callbacks

<ParamField body="onEvent" type="(event) => void">
Fires for each authoritative stream event.
</ParamField>

<ParamField body="onError" type="(error) => void">
Fires on turn failure.
</ParamField>

<ParamField body="onFinish" type="(snapshot) => void">
Fires when a turn settles with final snapshot.
</ParamField>

<ParamField body="onSessionChange" type="(session) => void">
Fires when `SessionState` advances; use to persist cursor.
</ParamField>

<ParamField body="prepareSend" type="PrepareSend">
Runs immediately before each `send`; return modified `SendTurnPayload`.
</ParamField>

<ParamField body="optimistic" type="boolean" default="true">
Project submitted user messages before `message.received`.
</ParamField>

### Resumable sessions

Persist the full `session` object (`sessionId`, `continuationToken`, `streamIndex`):

```tsx
const [initialSession] = useState(() => {
  const raw = localStorage.getItem("eve-session");
  return raw ? JSON.parse(raw) : undefined;
});

const agent = useEveAgent({
  initialSession,
  onSessionChange(session) {
    localStorage.setItem("eve-session", JSON.stringify(session));
  },
});
```

## Framework plugins

Framework plugins proxy `/eve/v1/*` to a local Eve dev server in development and to a private Eve Vercel service in production, keeping browser requests same-origin.

### Next.js — `eve/next`

Wrap `next.config.ts` with `withEve()`:

```ts
import type { NextConfig } from "next";
import { withEve } from "eve/next";

const nextConfig: NextConfig = {};
export default withEve(nextConfig);
```

| Option | Default | Purpose |
| --- | --- | --- |
| `eveRoot` | Next.js app root | Path to Eve agent directory |
| `eveBuildCommand` | `"eve build"` | Eve Vercel service build command |
| `configureVercelOutput` | `true` | Write `experimentalServices` to `.vercel/output/config.json` |
| `servicePrefix` | `"/_eve_internal/eve"` | Private Vercel route namespace |
| `devServerTimeoutMs` | `180000` | Dev server startup timeout |

Local production: `next build && next start` serves built Eve output on port `4274` (override with `EVE_NEXT_PRODUCTION_PORT`). Non-Vercel hosts: set `EVE_NEXT_PRODUCTION_ORIGIN`.

### Nuxt — `eve/nuxt`

```ts
export default defineNuxtConfig({
  modules: ["eve/nuxt"],
  eve: {
    eveRoot: "../my-agent",       // optional
    eveBuildCommand: "npm run build:eve",  // optional
  },
});
```

Auto-imports `useEveAgent` from `eve/vue`. Production overrides: `EVE_NUXT_PRODUCTION_ORIGIN`, `EVE_NUXT_PRODUCTION_PORT`.

### SvelteKit — `eve/sveltekit`

Register `eveSvelteKit()` before `sveltekit()` in `vite.config.ts`:

```ts
import { sveltekit } from "@sveltejs/kit/vite";
import { eveSvelteKit } from "eve/sveltekit";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [eveSvelteKit(), sveltekit()],
});
```

Dev proxy resolves `EVE_BASE_URL`, a healthy shared dev server, or spawns `eve dev --no-ui --port 0`. For separate-origin Eve in production, pass `host` to `useEveAgent`.

<Steps>
<Step title="Install eve and scaffold agent">
Install `eve`, run `eve init` if needed, and confirm `agent/` exists at the app root or set `eveRoot`.
</Step>
<Step title="Mount routes with framework plugin">
Add `withEve`, `eve/nuxt`, or `eveSvelteKit` so `/eve/v1/*` is same-origin.
</Step>
<Step title="Wire the hook or client">
Use `useEveAgent` for browser UIs or `Client` + `ClientSession` for scripts and custom integrations.
</Step>
<Step title="Configure auth on both sides">
Match client `auth`/`headers` to the Eve channel policy in `agent/channels/eve.ts`.
</Step>
<Step title="Persist session cursor">
Store `agent.session` via `onSessionChange` for reload survival.
</Step>
</Steps>

## When to use which surface

| Use case | Surface |
| --- | --- |
| Browser chat UI with streaming state | `useEveAgent` + framework plugin |
| Backend job, eval, test harness | `Client` + `ClientSession` |
| Custom UI projection shape | `useEveAgent({ reducer })` or manual `for await` over `MessageResponse` |
| Attach to in-progress stream after reload | `client.session(persistedState).stream()` |
| Agent discovery from a script | `client.info()` |

<Tip>
`EveMessage[]` from the default reducer follow AI SDK `UIMessage` conventions and drop into AI SDK UI primitives that accept `UIMessage[]`.
</Tip>

## Related pages

<CardGroup>
<Card title="Sessions and streaming" href="/sessions-and-streaming">
continuationToken vs sessionId, POST routes, NDJSON vocabulary, and reconnect semantics.
</Card>
<Card title="HTTP API reference" href="/http-api">
Stable `/eve/v1` routes, request/response shapes, and event vocabulary.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Route auth on the Eve channel, env vars, and production verification.
</Card>
<Card title="Security model" href="/security-model">
Fail-closed route auth defaults and trust boundaries.
</Card>
<Card title="TypeScript API reference" href="/typescript-api">
`define*` helpers, client entrypoints, and exported types.
</Card>
<Card title="Quickstart" href="/quickstart">
Shortest path from `eve init` to a running HTTP session.
</Card>
</CardGroup>

---

## 21. CLI reference

> All eve commands (init, info, build, start, dev, link, deploy, eval, channels), flags, exit codes, .eve/ artifact paths, and the recommended edit-info-dev-build-start loop.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/21-cli-reference.md
- Generated: 2026-06-16T19:30:34.422Z

### Source Files

- `docs/reference/cli.md`
- `packages/eve/src/cli/run.ts`
- `packages/eve/src/cli/commands/register-project-commands.ts`
- `packages/eve/src/cli/commands/init.ts`
- `packages/eve/src/cli/commands/info.ts`
- `packages/eve/src/evals/cli/eval.ts`

---
title: "CLI reference"
description: "All eve commands (init, info, build, start, dev, link, deploy, eval, channels), flags, exit codes, .eve/ artifact paths, and the recommended edit-info-dev-build-start loop."
---

The `eve` binary (`packages/eve/bin/eve.js`) resolves the application root from the current working directory and registers commands through Commander. Invoking `eve` with no subcommand runs `eve dev`. Commands that start servers or run evals load development environment files from the app root before executing.

## Command summary

| Command | Description |
| --- | --- |
| `eve init [target]` | Scaffold a new agent, or add one to an existing project directory |
| `eve info` | Print resolved application paths, discovered surface, messaging routes, and artifact locations |
| `eve build` | Compile `.eve/` artifacts and build host output; prints the output directory |
| `eve start` | Serve the built `.output/` app; prints the listening URL |
| `eve dev` | Start the local dev server and open the terminal UI |
| `eve dev <url>` | Connect the UI to an existing server URL instead of booting locally |
| `eve link` | Link the directory to a Vercel project and pull AI Gateway credentials |
| `eve deploy` | Deploy the agent to Vercel production (links first when needed) |
| `eve eval` | Run evals against the local app or a remote target |
| `eve channels add [kind]` | Scaffold a channel interactively, or by kind (`slack` \| `web`) |
| `eve channels list` | List user-authored channels |

Global flags apply to the root program:

| Flag | Description |
| --- | --- |
| `-V, --version` | Print the installed `eve` package version |
| `-h, --help` | Print command help |

## Environment loading

`eve build`, `eve start`, `eve dev`, and `eve eval` load development environment files from the app root. Files are merged in this order (later files override earlier ones; parent-process variables set before the first load keep precedence):

1. `.env`
2. `.env.development`
3. `.env.local`
4. `.env.development.local`

`eve info`, `eve init`, `eve link`, `eve deploy`, and `eve channels` do not load these files at the command handler. The dev and production servers reload env files when their watchers detect changes.

## Exit codes

| Code | When |
| --- | --- |
| `0` | Command completed successfully; help requested (`--help`); `eve eval` when every eval passed |
| `1` | Runtime or validation error; agent-scoped command run outside an Eve project; `eve link`/`eve deploy`/`eve channels` failure; `eve eval` when any eval failed or `--strict` threshold miss |
| `2` | `eve eval` configuration errors (no evals discovered, no evals matching filters, invalid numeric flags) |

Uncaught errors in the top-level binary set `process.exitCode = 1`. `eve eval` calls `process.exit()` with the resolved code after the run completes.

<Warning>
Agent-scoped commands (`eve link`, `eve deploy`, `eve channels add`, `eve channels list`) exit `1` with this message when the working directory has no Eve agent:

`No Eve agent in this directory. Run eve init <name>, then run this command from inside the new project.`
</Warning>

## `.eve/` artifact paths

Compiler-owned artifacts are written under `.eve/` during discovery and compile. They are preserved even on partial failure so diagnostics remain inspectable.

:::files
.eve/
├── discovery/
│   ├── agent-discovery-manifest.json   # What Eve found on disk
│   └── diagnostics.json              # Authored-shape errors and warnings
├── compile/
│   ├── compiled-agent-manifest.json  # Serialized surface the runtime loads
│   ├── compile-metadata.json         # Build metadata, status, and digests
│   ├── module-map.mjs                # Compiled module entrypoints
│   ├── channel-instrumentation.d.ts  # Channel instrumentation types
│   └── workspace-resources/          # Materialized workspace skill resources
├── dev-process.pid                   # Active `eve dev` process (local dev only)
├── dev-runtime/
│   ├── current.json                  # Pointer to active runtime snapshot
│   └── snapshots/                    # Immutable dev runtime source snapshots
├── evals/
│   └── <timestamp>/                  # Per-run eval artifacts (summary, results)
├── nitro/                            # Nitro build cache (dev and build)
├── nitro-output/                     # Staged Nitro surface output (Vercel builds)
├── sandbox-cache/                    # Local sandbox templates and snapshots
├── agent-summary.json                # Vercel build agent summary
└── bootstrap.mjs                     # Compiled artifacts bootstrap
:::

Production host output lands outside `.eve/`:

| Path | When |
| --- | --- |
| `.output/` | Local `eve build` / `eve start` |
| `.vercel/output/` | Builds running with `VERCEL` set |

`eve info` prints artifact paths, workflow build directory, output directory, and the active messaging route contract.

## Recommended loop

<Steps>
<Step title="Edit agent files">
Change instructions, skills, tools, connections, channels, or `agent/agent.ts` under `agent/`.
</Step>
<Step title="Verify discovery">
Run `eve info` to confirm files were discovered and read diagnostics before starting a server.
</Step>
<Step title="Iterate locally">
Run `eve dev` for watch-mode rebuilds, sandbox prewarm, and the terminal UI.
</Step>
<Step title="Build for shipping">
Run `eve build` to compile `.eve/` artifacts and produce host output.
</Step>
<Step title="Smoke-test production output">
Run `eve start` against the built `.output/` directory before deploying.
</Step>
</Steps>

When `eve build` fails on discovery errors, the compiler prints a full diagnostics report (severity, message, source path) and the diagnostics artifact path.

## `eve init`

```bash
eve init [target] [--channel-web-nextjs]
```

Creates a new Eve agent or adds one to an existing project. No prompts; dependencies install automatically.

**Target modes**

| Target | Behavior |
| --- | --- |
| `my-agent` (name) | Scaffold a fresh project in `my-agent/` |
| `.` or existing directory | Add an agent to that project (`package.json` required; `agent/` must not exist) |
| Omitted | Same as `eve init .` |

Either mode installs dependencies, initializes Git for fresh scaffolds, and runs `eve dev` through the project's package manager. When launched by a coding agent, init prints the dev command instead of spawning the TUI.

<ParamField body="--channel-web-nextjs" type="flag">
Add the Web Chat application (Next.js). Rejected when adding to an existing project; run `eve channels add web` afterward.
</ParamField>

## `eve info`

```bash
eve info [--json]
```

Prints resolved application paths, compile status, discovery summary, artifact locations, and messaging routes (`POST` create/continue, `GET` stream). Run this first when discovery or routing behaves unexpectedly.

<ParamField body="--json" type="flag">
Emit the stable machine-readable contract: `appRoot`, `agentRoot`, `layout`, `status`, `diagnostics`, `model`, `instructions`, `skills`, `tools`, `channels`, `messaging`, and `artifacts`.
</ParamField>

## `eve build`

```bash
eve build
```

No flags. Compiles discovery and compile artifacts under `.eve/`, builds Nitro host output, and prints `built output at <path>`. On Vercel (`VERCEL` set), also runs sandbox prewarm, emits workflow functions, and writes `.vercel/output/`.

## `eve start`

```bash
eve start [--host <host>] [--port <port>]
```

Serves the previously built `.output/server/index.mjs`. Loads development env files, prewarms built-app sandboxes, polls `/eve/v1/health`, and blocks until SIGINT/SIGTERM or the server exits. Prints `server listening at <url>`.

<ParamField body="--host" type="string">
Host interface to bind. Defaults to `0.0.0.0`.
</ParamField>

<ParamField body="--port" type="number">
Port to listen on. Defaults to `$PORT`, then `3000`.
</ParamField>

## `eve dev`

```bash
eve dev [options]
eve dev https://your-app.example.com
```

Starts the development Nitro server with authored-source watching, runtime snapshot staging, and optional terminal UI. Pass a bare URL as the only argument (equivalent to `--url`) to connect the UI to a remote deployment.

<Note>
Default bind address is `127.0.0.1` and default port is `$PORT` then `2000`. The interactive UI is disabled on non-TTY terminals (`--no-ui` or piped stdout).
</Note>

Local dev writes the active server PID to `.eve/dev-process.pid`. Starting a second `eve dev` while that process is running exits with the PID and a `kill` command. Runtime snapshots live under `.eve/dev-runtime/snapshots/` so in-flight sessions keep a consistent code revision while new prompts pick up rebuilds.

<ParamField body="--host" type="string">
Host interface to bind. Cannot be used with `--url`.
</ParamField>

<ParamField body="--port" type="number">
Port to listen on. Cannot be used with `--url`. Defaults to `$PORT`, then `2000`. When omitted, Eve retries on port conflicts.
</ParamField>

<ParamField body="-u, --url" type="string">
Connect the terminal UI to an existing server URL instead of starting one. Cannot combine with `--host`, `--port`, or `--no-ui`.
</ParamField>

<ParamField body="--no-ui" type="flag">
Start the server without the interactive UI. Headless mode prints `server listening at <url>`.
</ParamField>

<ParamField body="--name" type="string">
Title shown in the terminal UI. Defaults to the app folder name (or remote host for `--url`).
</ParamField>

<ParamField body="--input" type="string">
Pre-fill the prompt input after launching the UI. Requires the interactive UI.
</ParamField>

<ParamField body="--tools" type="enum">
Tool-call rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `auto-collapsed`.
</ParamField>

<ParamField body="--reasoning" type="enum">
Reasoning rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `full`.
</ParamField>

<ParamField body="--subagents" type="enum">
Subagent-section rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `auto-collapsed`.
</ParamField>

<ParamField body="--connection-auth" type="enum">
Connection-authorization rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `full`.
</ParamField>

<ParamField body="--assistant-response-stats" type="enum">
Assistant header statistic: `tokens` | `tokensPerSecond`. Default: `tokensPerSecond`.
</ParamField>

<ParamField body="--context-size" type="number">
Model context window size, shown as a usage percentage in the UI.
</ParamField>

<ParamField body="--logs" type="enum">
Server/agent logs to show: `all` | `stderr` | `sandbox` | `none`. Default: `stderr`.
</ParamField>

<Tip>
For manual cleanup after stopping `eve dev`, delete `.eve/dev-runtime/snapshots/` or `.eve/sandbox-cache/local/templates/`.
</Tip>

## `eve link`

```bash
eve link
```

Interactive only. Walks Vercel team and project pickers, runs `vercel link`, pulls environment variables, and verifies an AI Gateway credential (`VERCEL_OIDC_TOKEN` or `AI_GATEWAY_API_KEY`) landed in `.env.local`. Re-linking always runs the pickers; the new choice wins. A running `eve dev` reloads env files automatically.

In CI, use `vercel link --project <name> --yes` instead.

## `eve deploy`

```bash
eve deploy
```

Deploys to Vercel production (`vercel deploy --prod`). Installs dependencies and pulls environment variables. An already-linked project deploys with or without a TTY. An unlinked directory runs the `eve link` pickers when a terminal is present; otherwise exits `1` with guidance to run `eve link` first.

## `eve eval`

```bash
eve eval [evalId...] [--url <url>] [options]
```

Runs all discovered evals when no ids are given. Ids match exactly or by directory prefix (`eve eval weather` runs everything under `evals/weather/`). Eval files use the `*.eval.ts` extension under `evals/`.

Without `--url`, eval starts a local dev server on `127.0.0.1` with port `0` (ephemeral), runs evals, then closes the server.

<ParamField body="--url" type="string">
Remote agent URL. Skips local host startup.
</ParamField>

<ParamField body="--tag" type="string[]">
Run only evals carrying any of the given tags.
</ParamField>

<ParamField body="--strict" type="flag">
Below-threshold scores also fail the exit code (exit `1`).
</ParamField>

<ParamField body="--list" type="flag">
Print discovered evals without running them.
</ParamField>

<ParamField body="--timeout" type="number">
Per-eval timeout in milliseconds (non-negative integer).
</ParamField>

<ParamField body="--max-concurrency" type="number">
Max concurrent eval executions (positive integer). Default: `8` when neither flag nor `evals.config.ts` sets it.
</ParamField>

<ParamField body="--json" type="flag">
Output run summary as JSON.
</ParamField>

<ParamField body="--junit" type="string">
Write JUnit XML results to the given file path.
</ParamField>

<ParamField body="--skip-report" type="flag">
Skip eval-defined reporters (for example Braintrust).
</ParamField>

<ParamField body="--verbose" type="flag">
Stream per-eval `t.log` lines to stdout.
</ParamField>

Run artifacts are written under `.eve/evals/<timestamp>/` (`summary.json`, `results.jsonl`, per-eval detail files).

## `eve channels add`

```bash
eve channels add [kind] [-f] [-y]
```

Scaffolds a channel into `agent/channels/`. Known kinds: `slack`, `web`. With no `kind`, prompts interactively (requires a TTY). The flow may link to Vercel, configure services, and deploy when adding channels.

<ParamField body="-f, --force" type="flag">
Overwrite existing channel files.
</ParamField>

<ParamField body="-y, --yes" type="flag">
Assume yes for confirmations. Requires an explicit `kind`.
</ParamField>

## `eve channels list`

```bash
eve channels list [--json]
```

Lists user-authored channel names from the current project.

<ParamField body="--json" type="flag">
Output `{ channels: string[] }` as JSON.
</ParamField>

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Scaffold with `eve init`, verify with `eve info`, iterate with `eve dev`, and create HTTP sessions.
</Card>
<Card title="Project layout" href="/project-layout">
Authored slots under `agent/` and what compiles into `.eve/` artifacts.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
`eve link`/`eve deploy` flows, Vercel output, and production verification.
</Card>
<Card title="Instrumentation and evals" href="/instrumentation-and-evals">
Authoring evals, judges, and `eve eval` reporters.
</Card>
<Card title="Channels" href="/channels">
`defineChannel` authoring and platform channel factories.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Discovery diagnostics, dev server PID conflicts, and common failure modes.
</Card>
</CardGroup>

---

## 22. TypeScript API reference

> Exported define* helpers and import paths from packages/eve/src/public, ctx members, approval predicates, built-in tool defaults, and eval/client entrypoints.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/22-typescript-api-reference.md
- Generated: 2026-06-16T19:30:37.098Z

### Source Files

- `docs/reference/typescript-api.md`
- `packages/eve/src/public/index.ts`
- `packages/eve/src/public/definitions/agent.ts`
- `packages/eve/src/public/definitions/tool.ts`
- `packages/eve/src/public/tools/index.ts`
- `packages/eve/src/public/channels/index.ts`

---
title: "TypeScript API reference"
description: "Exported define* helpers and import paths from packages/eve/src/public, ctx members, approval predicates, built-in tool defaults, and eval/client entrypoints."
---

The `eve` package exposes a filesystem-first authoring API: you import `define*` helpers, default-export the result from slots under `agent/`, and the compiler derives identity from file paths. The authoritative export surface is `packages/eve/src/public/` (re-exported through `eve` subpath imports in `package.json`). Anything not exported there is a framework internal.

## Path-derived identity

Definitions do not carry `name` or `id` fields. The runtime name comes from the file path:

| Authored path | Runtime identity |
| --- | --- |
| `agent/tools/get_weather.ts` | tool `get_weather` |
| `agent/connections/linear.ts` | connection `linear` |
| `agent/channels/slack.ts` | channel `slack` |
| `agent/skills/deploy.md` | skill `deploy` |
| `evals/smoke/basic.eval.ts` | eval `smoke/basic` |

The root agent identity is derived at compile time from `manifest.agentId` (package name or app-root basename), not from a field in `defineAgent`.

## Authoring pattern

Most authored files follow the same shape: import a helper, default-export the result.

```ts title="agent/agent.ts"
import { defineAgent } from "eve";

export default defineAgent({ model: "anthropic/claude-opus-4.8" });
```

```ts title="agent/tools/get_weather.ts"
import { defineTool } from "eve/tools";
import { z } from "zod";

export default defineTool({
  description: "Get the weather for a city.",
  inputSchema: z.object({ city: z.string() }),
  async execute({ city }, ctx) {
    return { city, condition: "Sunny" };
  },
});
```

`define*` helpers are identity functions at runtime. They preserve literal types, reject unknown keys at compile time, and stamp brands the compiler and lifecycle code validate.

## define* helpers

| Helper | Import from | Authored at |
| --- | --- | --- |
| `defineAgent` | `eve` | `agent/agent.ts` |
| `defineRemoteAgent` | `eve` | `agent/subagents/<id>/agent.ts` |
| `defineTool` | `eve/tools` | `agent/tools/<name>.ts` |
| `defineDynamic` | `eve/tools`, `eve/skills`, `eve/instructions` | matching `agent/` slot |
| `defineMcpClientConnection`, `defineOpenAPIConnection` | `eve/connections` | `agent/connections/<name>.ts` |
| `defineChannel` | `eve/channels` | `agent/channels/<name>.ts` |
| `eveChannel`, `slackChannel`, and other platform factories | `eve/channels/<platform>` | `agent/channels/<platform>.ts` |
| `defineSkill` | `eve/skills` | `agent/skills/<name>.ts` or `.md` |
| `defineInstructions` | `eve/instructions` | `agent/instructions.ts` or `agent/instructions/` |
| `defineHook` | `eve/hooks` | `agent/hooks/<slug>.ts` |
| `defineSchedule` | `eve/schedules` | `agent/schedules/<name>.ts` or `.md` |
| `defineState` | `eve/context` | module scope in tools, hooks, or lifecycle code |
| `defineSandbox` | `eve/sandbox` | `agent/sandbox.ts` |
| `defineInstrumentation` | `eve/instrumentation` | `agent/instrumentation.ts` |
| `defineEval` | `eve/evals` | `evals/<path>.eval.ts` |
| `defineEvalConfig` | `eve/evals` | `evals/evals.config.ts` |
| `useEveAgent` | `eve/react`, `eve/vue`, `eve/svelte` | frontend components |

### defineAgent

`defineAgent` accepts an additive configuration object. Key fields:

<ParamField body="model" type="string | LanguageModel" required>
  Primary model: an AI Gateway model ID or any AI SDK-compatible language model.
</ParamField>

<ParamField body="description" type="string">
  Human-readable purpose. Required for subagents; surfaced as the lowered subagent tool description.
</ParamField>

<ParamField body="compaction" type="object">
  Context compaction settings, including `thresholdPercent`.
</ParamField>

<ParamField body="modelOptions" type="object">
  Provider-specific model call options.
</ParamField>

<ParamField body="modelContextWindowTokens" type="number">
  Escape hatch when the gateway catalog cannot resolve context window metadata.
</ParamField>

<ParamField body="outputSchema" type="StandardJSONSchemaV1 | JsonObject">
  Structured return type for task-mode runs (subagents, schedules, remote jobs). Interactive turns ignore it unless the client supplies a per-message schema.
</ParamField>

<ParamField body="experimental" type="object">
  Opt-in unstable capabilities (for example `codeMode`).
</ParamField>

<ParamField body="build" type="object">
  Packaging options such as `externalDependencies`.
</ParamField>

Authentication and network policies belong on the channel that handles inbound requests, not on `defineAgent`.

### defineTool

`defineTool` schemas use Standard Schema (Zod, Valibot, and others). The public `ToolDefinition` shape includes:

<ParamField body="description" type="string" required>
  Model-facing tool description.
</ParamField>

<ParamField body="inputSchema" type="StandardJSONSchemaV1 | JsonObject" required>
  Validated tool input.
</ParamField>

<ParamField body="outputSchema" type="StandardJSONSchemaV1 | JsonObject">
  Optional structured output schema.
</ParamField>

<ParamField body="execute" type="(input, ctx) => TOutput" required>
  Handler receiving validated input and {@link ToolContext}.
</ParamField>

<ParamField body="needsApproval" type="(ctx) => boolean">
  Per-call HITL gate. Use `always`, `once`, or `never` from `eve/tools/approval`.
</ParamField>

<ParamField body="auth" type="ToolAuthDefinition">
  Authorization strategy. When set, `ctx.getToken()` and `ctx.requireAuth()` become available.
</ParamField>

<ParamField body="toModelOutput" type="(output) => ToolModelOutput">
  Projection controlling what the model sees as the tool result. Channel handlers always receive the full output.
</ParamField>

`defineTool` also exports `defineDynamic` (runtime resolver sentinel), `disableTool()` (opt out of a framework default by slug), and `ExperimentalWorkflow` (opt in to the experimental `Workflow` orchestration tool).

Tool customization factories are also exported from `eve/tools`: `defineBashTool`, `defineGlobTool`, `defineGrepTool`, `defineReadFileTool`, and `defineWriteFileTool`. Use `toolResultFrom` to narrow connection and MCP tool results in `execute`.

### defineDynamic

`defineDynamic` registers runtime resolvers keyed on stream events. The directory you author in determines return shape and which events fire:

| Slot | Return shape | Events honored |
| --- | --- | --- |
| `agent/tools/` | `defineTool(...)`, `Record<string, defineTool(...)>`, or `null` | `session.started`, `turn.started`, `step.started` |
| `agent/skills/` | `defineSkill(...)`, record map, or `null` | `session.started`, `turn.started` |
| `agent/instructions/` | `defineInstructions({ markdown })` or `null` | `session.started`, `turn.started` |

Return `null` to contribute nothing for that event. Map keys become compound slugs (`slug__key`).

### Other define* helpers

- **`defineRemoteAgent`** — declares a remote Eve deployment the parent calls as a subagent tool. Requires `description` and `url`; defaults `path` to `/eve/v1/session`. Optional `auth`, `headers`, and `outputSchema`.
- **`defineMcpClientConnection` / `defineOpenAPIConnection`** — MCP and OpenAPI integrations. Also exports `defineInteractiveAuthorization` and connection authorization error types.
- **`defineChannel`** — custom ingress. Route verbs `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `WS` are exported from `eve/channels`.
- **`defineHook`** — stream-event subscribers under `events:`. Handlers are observe-only; they cannot inject model context.
- **`defineSchedule`** — cron schedules with either `markdown` (fire-and-forget) or `run` (imperative handler with `receive`, `waitUntil`, `appAuth`).
- **`defineState`** — durable typed context keyed by a namespaced string. Names must not start with the reserved `eve.` prefix.
- **`defineSandbox`** — sandbox backend and lifecycle. Subpath backends: `eve/sandbox/vercel`, `eve/sandbox/docker`, `eve/sandbox/just-bash`, `eve/sandbox/microsandbox`.
- **`defineInstrumentation`** — OTel setup and stream callbacks. Re-exports `isChannel` and channel metadata types (canonical home is `eve/channels`).
- **`defineEval` / `defineEvalConfig`** — eval cases and run-wide defaults. Eval identity is path-derived; authoring `id` or `name` throws.

## Runtime context (`ctx`)

`ctx` is live only inside ALS-scoped harness execution: tool `execute`, hook handlers, and channel event handlers. Calling context APIs at module top level throws.

`SessionContext` is the shared base. `ToolContext` extends it with token accessors. `HookContext` adds agent and channel metadata.

### SessionContext members

| Member | Type | Use |
| --- | --- | --- |
| `session.id` | `string` | Current session identifier |
| `session.auth` | `SessionAuth` | Caller and initiator principals |
| `session.turn` | `SessionTurn` | Active turn metadata |
| `session.parent` | `SessionParent?` | Parent lineage for subagent sessions |
| `getSandbox()` | `Promise<SandboxSession>` | Live sandbox handle; throws when unavailable |
| `getSkill(identifier)` | `SkillHandle` | Named skill visible to the current agent |

### ToolContext additions

| Member | Use |
| --- | --- |
| `getToken()` | Resolves bearer token for the tool's declared `auth`. Cache miss on interactive strategies throws `ConnectionAuthorizationRequiredError`, suspending the turn for OAuth. Throws when the tool has no `auth`. |
| `requireAuth()` | Gates execution on authorization without resolving a token first. Always throws `ConnectionAuthorizationRequiredError`. |

### HookContext additions

| Member | Use |
| --- | --- |
| `agent.name` | Current agent name |
| `agent.nodeId` | Optional node identifier |
| `channel.kind` | Channel kind discriminator |
| `channel.continuationToken` | Active continuation token when present |

Callbacks outside ALS scope (schedule `run`, sandbox `bootstrap`/`onSession`, instrumentation `setup`) receive domain-specific arguments instead of `ctx`.

## Approval predicates

Import from `eve/tools/approval`. Pass the returned function to `needsApproval`:

```ts title="agent/tools/deploy.ts"
import { defineTool } from "eve/tools";
import { once } from "eve/tools/approval";

export default defineTool({
  description: "Deploy to production.",
  inputSchema: z.object({ target: z.string() }),
  needsApproval: once(),
  async execute(input, ctx) {
    /* ... */
  },
});
```

| Helper | Behavior |
| --- | --- |
| `always()` | Require approval on every call |
| `never()` | Never require approval |
| `once()` | Require approval until the user explicitly approves once per session. Keys off bare `toolName`, not compound approval keys. Denial leaves the tool unrecorded. |

`NeedsApprovalContext` exposes `approvedTools` (a `ReadonlySet<string>`), `toolName`, and optional `toolInput` for input-aware decisions.

Custom predicates can inspect `toolInput` for per-connection scoping or other input-dependent gates.

## Built-in tool defaults

Import plain `ToolDefinition` values from `eve/tools/defaults` to spread, wrap, or patch framework tools in `agent/tools/<slug>.ts`:

| Export | Framework tool | Notes |
| --- | --- | --- |
| `bash` | Shell execution | |
| `readFile` | File reader | Read-before-write stamps reset on compaction |
| `writeFile` | File writer | Enforces read-before-write and stale-read detection |
| `glob` | Path glob search | |
| `grep` | Content regex search | |
| `webFetch` | HTTP fetch | |
| `webSearch` | Provider-managed search | Local `execute` is a throwing stub; override with `defineTool` in `agent/tools/web_search.ts` |
| `todo` | Durable todo list | Spreading preserves framework closure-bound state |
| `loadSkill` | Skill loader | Only useful when the agent declares skills |

### Framework tools without defaults exports

The harness also ships tools that are registered internally rather than through `eve/tools/defaults`:

| Tool | Registration |
| --- | --- |
| `ask_question` | Framework tool in the default harness |
| `agent` | Subagent delegation (lowered from `agent/subagents/`) |
| `connection_search` | Framework dynamic resolver (not a static default export) |

To remove a framework default, export `disableTool()` as the default from `agent/tools/<matching-slug>.ts`. To enable the experimental `Workflow` tool, re-export `ExperimentalWorkflow` from `agent/tools/workflow.ts`.

## Channel auth helpers

Route-level authentication primitives live in `eve/channels/auth`:

| Helper | Use |
| --- | --- |
| `localDev()` | Anonymous auth on loopback hosts and Vercel development |
| `vercelOidc(opts?)` | Vercel OIDC bearer verification |
| `placeholderAuth()` | Scaffold that fails closed in production |
| `httpBasic(credentials)` | HTTP Basic verification |

Lower-level verifier functions (`verifyHttpBasic`, `verifyVercelOidc`, and others) are available for custom `fetch` handlers.

## Eval entrypoints (`eve/evals`)

| Import path | Exports |
| --- | --- |
| `eve/evals` | `defineEval`, `defineEvalConfig`, eval types, `EveEvalTurnFailedError` |
| `eve/evals/expect` | `includes`, `equals`, `matches`, `similarity` assertion builders |
| `eve/evals/reporters` | `Braintrust`, `JUnit`, `EvalReporter` |
| `eve/evals/loaders` | `loadJson`, `loadYaml` |

`defineEval` accepts a `test(t)` function that drives the agent (`t.send`, `t.respond`, …) and asserts on output (`t.completed()`, `t.check(...)`, `t.judge.autoevals.*`). Each eval file is one case; default-export an array for datasets.

`defineEvalConfig` is the required default export of `evals/evals.config.ts`. It supplies optional default `judge`, `reporters`, `maxConcurrency`, and `timeoutMs`. CLI flags and per-eval values override config defaults.

## Client entrypoints (`eve/client`)

| Export | Role |
| --- | --- |
| `Client` | HTTP client bound to one host and auth config |
| `ClientSession` | One conversation; tracks continuation token and stream cursor |
| `ClientError` | Non-success HTTP responses |
| `defaultMessageReducer` | Default NDJSON stream-to-message reducer |
| `EveAgentStore` | Stateful client store for UI integrations |
| `createDataUrlFilePart`, `createTextWithFileContent` | File part helpers |
| `MessageResponse` | Turn response wrapper |
| `resolveTextToResponse`, `resolveTextToResponses` | Text-to-input-response helpers |

`ClientOptions` fields:

<ParamField body="host" type="string" required>
  Base URL of the Eve agent server.
</ParamField>

<ParamField body="auth" type="ClientAuth">
  `{ bearer: TokenValue }` or `{ basic: { username, password } }`. Resolved before every request.
</ParamField>

<ParamField body="headers" type="HeadersValue">
  Static or per-request custom headers.
</ParamField>

<ParamField body="maxReconnectAttempts" type="number" default="3">
  Stream reconnection attempts per message turn.
</ParamField>

<ParamField body="preserveCompletedSessions" type="boolean" default="false">
  Keep continuation token after `session.completed` for follow-up prompts.
</ParamField>

`Client` methods: `health()`, `info()`, `createSession()`, and authenticated `fetch()`. `ClientSession.send()` accepts a string shorthand or a `SendTurnInput` object (message, HITL responses, client context, output schema, abort signal).

The client re-exports stream event types from the protocol so consumers can type-narrow without importing internals.

## Frontend hooks and framework plugins

| Import | Exports |
| --- | --- |
| `eve/react`, `eve/vue`, `eve/svelte` | `useEveAgent`, reducer types, message types |
| `eve/next` | `withEve()` Next.js config wrapper |
| `eve/nuxt` | Nuxt module default export |
| `eve/sveltekit` | `eveSvelteKit()` Vite plugin |

`useEveAgent` wraps `Client`/`ClientSession` with framework-native state, streaming reducers, and HITL input handling.

## Imports at a glance

| Import | Holds |
| --- | --- |
| `eve` | `defineAgent`, `defineRemoteAgent`, agent types |
| `eve/tools` | `defineTool`, `defineDynamic`, `disableTool`, `ExperimentalWorkflow`, tool types, `toolResultFrom`, tool factories |
| `eve/tools/defaults` | Built-in tools as plain values |
| `eve/tools/approval` | `always`, `once`, `never`, `NeedsApprovalContext` |
| `eve/connections` | Connection define helpers, auth types, authorization errors |
| `eve/channels` | `defineChannel`, route verbs, `isChannel`, channel metadata types |
| `eve/channels/eve` | `eveChannel` |
| `eve/channels/auth` | Route auth strategy helpers and verifiers |
| `eve/channels/{slack,discord,teams,telegram,twilio,github,linear}` | Platform channel factories |
| `eve/hooks` | `defineHook`, `HookContext` |
| `eve/schedules` | `defineSchedule` |
| `eve/skills` | `defineSkill`, `defineDynamic`, `SkillHandle` |
| `eve/instructions` | `defineInstructions`, `defineDynamic` |
| `eve/context` | `defineState`, `StateHandle`, session types |
| `eve/sandbox` | `defineSandbox`, backends, sandbox types |
| `eve/instrumentation` | `defineInstrumentation`, `isChannel` |
| `eve/evals` | Eval define helpers and types |
| `eve/client` | `Client`, `ClientSession`, reducers, stream types |
| `eve/agents/auth` | `OutboundAuthFn` for remote agent auth |

Exported types ship from the same entrypoint as the helper they describe (for example `ToolDefinition` from `eve/tools`, `AgentDefinition` from `eve`).

## Related pages

<CardGroup cols={2}>
  <Card title="Configure agent.ts" href="/agent-configuration">
    `defineAgent` fields, compaction, model options, and build packaging.
  </Card>
  <Card title="Tools" href="/tools">
    `defineTool` execution, auth, approval, and dynamic resolution.
  </Card>
  <Card title="State, hooks, and session context" href="/state-hooks-and-context">
    `defineState`, `defineHook`, and where `ctx` APIs are valid.
  </Card>
  <Card title="Default harness" href="/default-harness">
    Built-in tools, compaction defaults, and override patterns.
  </Card>
  <Card title="Instrumentation and evals" href="/instrumentation-and-evals">
    `defineEval`, judges, assertions, and reporters.
  </Card>
  <Card title="Client integration" href="/client-integration">
    `Client`, `ClientSession`, `useEveAgent`, and framework plugins.
  </Card>
</CardGroup>

---

## 23. HTTP API reference

> Stable /eve/v1 routes (health, info, session create/continue/stream, OAuth callbacks), request/response shapes, NDJSON event vocabulary, and dev-only runtime-artifact and schedule-dispatch endpoints.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/23-http-api-reference.md
- Generated: 2026-06-16T19:30:53.030Z

### Source Files

- `packages/eve/src/protocol/routes.ts`
- `docs/concepts/sessions-runs-and-streaming.md`
- `packages/eve/src/internal/nitro/routes/health.ts`
- `packages/eve/src/internal/nitro/routes/info.ts`
- `packages/eve/src/internal/nitro/routes/agent-info/build-agent-info-response.ts`
- `packages/eve/src/internal/nitro/routes/dev-runtime-artifacts.ts`

---
title: HTTP API reference
description: Stable /eve/v1 routes (health, info, session create/continue/stream, OAuth callbacks), request/response shapes, NDJSON event vocabulary, and dev-only runtime-artifact and schedule-dispatch endpoints.
---

Every Eve deployment exposes a stable HTTP surface under `/eve/v1`. Framework-owned routes handle health checks, agent inspection, session lifecycle, OAuth redirects, and (during local development) runtime-artifact and schedule-dispatch controls. Session create, continue, and stream routes are registered by the default `eveChannel` in `agent/channels/eve.ts`; their auth policy is whatever you configure on that channel.

## Route map

All stable routes share the prefix `/eve/v1`. Dev-only routes mount only when Nitro runs in dev mode (`eve dev`); production builds never register them.

| Method | Path | Auth | Availability |
| --- | --- | --- | --- |
| `GET` | `/eve/v1/health` | None | Always |
| `GET` | `/eve/v1/info` | `localDev()` then `vercelOidc()` | Always |
| `POST` | `/eve/v1/session` | `eveChannel` auth chain | Always |
| `POST` | `/eve/v1/session/:sessionId` | `eveChannel` auth chain | Always |
| `GET` | `/eve/v1/session/:sessionId/stream` | `eveChannel` auth chain | Always |
| `GET` or `POST` | `/eve/v1/connections/:name/callback/:token` | None (token is the capability) | Always |
| `POST` | `/eve/v1/callback/:token` | None (token is the capability) | Always |
| `GET` | `/eve/v1/dev/runtime-artifacts` | None | Dev only |
| `POST` | `/eve/v1/dev/runtime-artifacts/rebuild` | None | Dev only |
| `POST` | `/eve/v1/dev/schedules/:scheduleId` | None | Dev only |

Authored channels (`defineChannel`, Slack, Discord, and others) register additional routes outside this table. This page covers only the framework-owned `/eve/v1` surface.

## Authentication

Routes fall into three auth classes:

1. **Always public** — `GET /eve/v1/health`, OAuth/callback routes, and all dev-only routes. Callback routes are intentionally unauthenticated: the unguessable `:token` segment is the capability that authorizes resuming parked workflow work.
2. **Default inspection auth** — `GET /eve/v1/info` walks `[localDev(), vercelOidc()]`. Loopback requests succeed locally; deployed Vercel targets require a valid OIDC bearer.
3. **Channel-configured auth** — Session routes run the `auth` array you pass to `eveChannel`. `routeAuth` walks the array in order; the first strategy returning a `SessionAuthContext` wins. Skipped entries (`null`/`undefined`) continue to the next. Exhaustion returns `401`. Include `none()` last to accept anonymous traffic.

Scaffolded apps typically ship `[localDev(), vercelOidc(), placeholderAuth()]`. Replace `placeholderAuth()` before serving real users. See [Auth and deployment](/auth-and-deployment) for strategy helpers and production patterns.

## Two handles

Session HTTP uses two distinct identifiers:

- **`continuationToken`** — Resume handle for sending follow-up messages or HITL responses. Required on `POST /eve/v1/session/:sessionId`. One active continuation per session; stale tokens are rejected.
- **`sessionId`** — Stream-and-inspect handle for attaching to the NDJSON event feed. Returned in JSON and the `x-eve-session-id` response header.

For the full delivery contract (ordering, reconnect, subagent child streams), see [Sessions and streaming](/sessions-and-streaming).

---

:::endpoint GET /eve/v1/health
Liveness probe for load balancers and uptime monitors. Always public.
:::

<RequestExample>

```bash
curl http://127.0.0.1:3000/eve/v1/health
```

</RequestExample>

<ResponseExample>

```json
{
  "ok": true,
  "status": "ready",
  "workflowId": "<workflow-entry-id>"
}
```

</ResponseExample>

<ResponseField name="ok" type="boolean">
Always `true` when the handler responds.
</ResponseField>

<ResponseField name="status" type="string">
Runtime readiness label. Currently `"ready"`.
</ResponseField>

<ResponseField name="workflowId" type="string">
The workflow entry reference id for the running agent.
</ResponseField>

---

:::endpoint GET /eve/v1/info
JSON inspection snapshot of the compiled agent: model, instructions, tools, skills, channels, schedules, subagents, sandbox, connections, hooks, workflow, workspace metadata, and discovery diagnostics.
:::

Auth matches the default Eve channel: loopback in local dev, Vercel OIDC in deployed targets. Response is never cached (`cache-control: no-store`).

<RequestExample>

```bash
curl http://127.0.0.1:3000/eve/v1/info
```

</RequestExample>

The top-level payload uses `kind: "eve-agent-info"` and `version: 1`. Key sections:

<ResponseField name="agent" type="object">
Name, description, model (`id`, `contextWindowTokens`, `providerOptions`, `routing`, `endpoint`), `outputSchema`, and source paths (`agentRoot`, `appRoot`).
</ResponseField>

<ResponseField name="tools" type="object">
`authored`, `framework`, `available`, `disabledFramework`, `dynamic` resolvers, and `reserved` tool names.
</ResponseField>

<ResponseField name="capabilities.devRoutes" type="boolean">
`true` in development mode, `false` in production. Indicates whether dev-only `/eve/v1/dev/*` routes are mounted.
</ResponseField>

<ResponseField name="diagnostics" type="object">
`discoveryErrors` and `discoveryWarnings` counts from agent discovery.
</ResponseField>

`eve info` and `eve build` surface the same discovery data offline. Use this route to inspect a *running* deployment.

---

:::endpoint POST /eve/v1/session
Create a new durable session and enqueue the first turn.
:::

Runs `routeAuth`, parses the JSON body, optionally checks upload policy for file attachments, then dispatches to the runtime. Returns immediately with handles; stream progress arrives on the message stream route.

<ParamField body="message" type="string | UserContent[]" required>
Plain text or an array of `text` / `file` parts. File parts require `mediaType` and `data` (base64, data URL, or HTTP URL). Framework-internal ref schemes (`eve-url:`, `eve-sandbox:`, `eve-attachment:`) are rejected.
</ParamField>

<ParamField body="clientContext" type="string | string[] | object">
One-turn client/page context. Strings and arrays are prefixed `Client context:\n` and injected as user context. Objects are JSON-stringified first.
</ParamField>

<ParamField body="outputSchema" type="object">
Optional JSON Schema object for a structured turn result. When set, the finalized payload appears on `result.completed` in the stream.
</ParamField>

<ParamField body="mode" type="'conversation' | 'task'">
Session mode. Defaults to `"conversation"` (enables `requestInput` for HITL). `"task"` runs without interactive input requests.
</ParamField>

<ParamField body="callback" type="object">
Optional remote subagent callback descriptor for `defineRemoteAgent` flows. Requires `callId`, `subagentName`, `token`, and `url` (absolute URL pointing at `POST /eve/v1/callback/:token` with matching token).
</ParamField>

<RequestExample>

```bash
curl -X POST http://127.0.0.1:3000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Summarize the latest forecast."}'
```

</RequestExample>

<ResponseExample>

```json
{
  "ok": true,
  "sessionId": "sess_…",
  "continuationToken": "…"
}
```

</ResponseExample>

| Status | Meaning |
| --- | --- |
| `202` | Session created and turn enqueued |
| `400` | Invalid JSON, missing `message`, bad attachment, or upload policy violation (`413` too large, `415` bad media type) |
| `401` / `403` | Auth walk failed |
| `204` | `onMessage` returned `null` — message accepted but not dispatched |
| `500` | `onMessage` handler threw |

<ResponseField name="continuationToken" type="string">
Opaque resume token for follow-up requests. Store it client-side.
</ResponseField>

<ResponseField name="sessionId" type="string">
Durable session id. Also returned in the `x-eve-session-id` header.
</ResponseField>

Default upload policy: 25 MB per attachment, all media types. Configure via `eveChannel({ uploadPolicy })`.

---

:::endpoint POST /eve/v1/session/:sessionId
Continue an existing session with a follow-up message, HITL responses, or both.
:::

<ParamField path="sessionId" type="string" required>
The durable session id from create or prior continue responses.
</ParamField>

<ParamField body="continuationToken" type="string" required>
Current resume token. Stale or missing tokens are rejected.
</ParamField>

<ParamField body="message" type="string | UserContent[]">
Follow-up user message. Optional when `inputResponses` is provided.
</ParamField>

<ParamField body="inputResponses" type="InputResponse[]">
Non-empty array of HITL responses. Each entry requires `requestId`; include `optionId` and/or `text` as appropriate.
</ParamField>

<ParamField body="clientContext" type="string | string[] | object">
Same semantics as session create.
</ParamField>

<ParamField body="outputSchema" type="object">
Per-turn structured output schema override.
</ParamField>

At least one of `message` or `inputResponses` must be non-empty.

<RequestExample>

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

</RequestExample>

<ResponseExample>

```json
{
  "ok": true,
  "sessionId": "<sessionId>"
}
```

</ResponseExample>

Returns `200` on success. The continuation token does not rotate in the response body — keep using the token you already hold until the channel emits a new one through stream events. Wait for `session.waiting` before sending another message for deterministic ordering.

---

:::endpoint GET /eve/v1/session/:sessionId/stream
Replay or live-attach to a session's NDJSON event feed.
:::

<ParamField path="sessionId" type="string" required>
Session to stream.
</ParamField>

<ParamField query="startIndex" type="integer">
Non-negative event index for reconnect or rewind. Omit to stream from the latest live position.
</ParamField>

<RequestExample>

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

</RequestExample>

<ResponseExample>

```text
{"type":"session.started","data":{},"meta":{"at":"2026-06-16T12:00:00.000Z"}}
{"type":"turn.started","data":{"sequence":0,"turnId":"turn_…"},"meta":{"at":"…"}}
{"type":"message.received","data":{"message":"…","sequence":1,"turnId":"turn_…"},"meta":{"at":"…"}}
```

</ResponseExample>

### Stream headers

| Header | Value |
| --- | --- |
| `content-type` | `application/x-ndjson; charset=utf-8` |
| `x-eve-stream-format` | `ndjson` |
| `x-eve-stream-version` | `15` |
| `x-eve-session-id` | Session id |
| `cache-control` | `no-store, no-transform` |
| `x-accel-buffering` | `no` |

Each line is one JSON object. Persisted events include `meta.at` (ISO timestamp). The stream is durable and fully replayable.

### Turn boundary events

Stop reading (or reconnect with an updated `startIndex`) when you see:

- `session.waiting` — parked, ready for the next user message
- `session.completed` — terminal success
- `session.failed` — terminal failure

## NDJSON event vocabulary

Events are typed by the `type` field. Most carry `data.sequence`, `data.turnId`, and often `data.stepIndex` for ordering within a turn.

### Session and turn lifecycle

| Event | Meaning |
| --- | --- |
| `session.started` | Durable session created. `data.runtime` may include agent id, model id, Eve version, and build metadata. Child subagent sessions include `data.invocation`. |
| `turn.started` | New turn began (`data.turnId`, `data.sequence`). |
| `message.received` | Inbound user message accepted (`data.message` is a text summary for multimodal input). |
| `turn.completed` | Turn finished successfully. |
| `turn.failed` | Turn failed (`data.code`, `data.message`, optional `data.details`). |
| `session.waiting` | Parked with `data.wait: "next-user-message"`. |
| `session.completed` | Terminal session success. |
| `session.failed` | Terminal session failure (`data.code`, `data.message`, `data.sessionId`). |

### Model steps

| Event | Meaning |
| --- | --- |
| `step.started` | Model call began. |
| `step.completed` | Model call finished (`data.finishReason`, optional `data.usage` token counts). |
| `step.failed` | Model call failed (`data.code`, `data.message`). |

`finishReason` values: `stop`, `tool-calls`, `length`, `content-filter`, `error`, `other`. `tool-calls` is the only non-terminal assistant step in the default harness.

### Assistant output

| Event | Meaning |
| --- | --- |
| `message.appended` | Text delta (`data.messageDelta`, cumulative `data.messageSoFar`). |
| `message.completed` | Finalized text block (`data.message`, `data.finishReason`). May fire multiple times per turn. |
| `reasoning.appended` | Reasoning delta (`data.reasoningDelta`, `data.reasoningSoFar`). |
| `reasoning.completed` | Finalized reasoning block. |
| `result.completed` | Structured output when `outputSchema` was requested (`data.result`). |

### Tools and human input

| Event | Meaning |
| --- | --- |
| `actions.requested` | Model requested tool calls (`data.actions`). |
| `action.result` | Tool result (`data.result`, `data.status`: `"completed"` \| `"failed"`, optional `data.error`). |
| `input.requested` | Run paused for HITL approval or `ask_question` (`data.requests`). |

Each `InputRequest` includes `requestId`, `prompt`, optional `options` (`id`, `label`, `description`, `style`), `display` hint (`confirmation` \| `select` \| `text`), and the underlying `action`.

### Subagents

| Event | Meaning |
| --- | --- |
| `subagent.called` | Child workflow session started (`data.childSessionId` — attach to that session's stream). |
| `subagent.started` | Inline subagent execution began. |
| `subagent.event` | Wraps a child stream event under `data.event` (inline subagents). |
| `subagent.completed` | Inline subagent finished (`data.output`). |

Delegated workflow subagents publish progress on their own child-session stream. The parent only signals attachment via `subagent.called`.

### Connections and compaction

| Event | Meaning |
| --- | --- |
| `authorization.required` | Connection needs OAuth (`data.name`, `data.description`, `data.authorization` challenge with optional `url`, `userCode`, `expiresAt`, `instructions`, `displayName`). |
| `authorization.completed` | Authorization resolved (`data.outcome`: `authorized` \| `declined` \| `failed` \| `timed-out`). |
| `compaction.requested` | Context compaction began (`data.modelId`, `data.usageInputTokens`). |
| `compaction.completed` | Compaction checkpoint written. |

---

:::endpoint GET|POST /eve/v1/connections/:name/callback/:token
OAuth IdP redirect target for in-turn connection authorization.
:::

`:name` is the path-derived connection name. `:token` is the workflow hook token minted when authorization starts. The handler projects query (and form-post body) params into an `authorizationCallback` payload, calls `resumeHook(token, …)`, and returns an HTML "Authorization complete" page.

No Eve credentials are required — the token is the unguessable capability. Returns `404` when no matching authorization is pending.

---

:::endpoint POST /eve/v1/callback/:token
Terminal callback for remote subagent sessions.
:::

Used by `defineRemoteAgent` when the framework POSTs completion results back to the parent agent. Body shape:

<ParamField body="callId" type="string" required>
Subagent call identifier.
</ParamField>

<ParamField body="subagentName" type="string" required>
Remote subagent name.
</ParamField>

<ParamField body="kind" type="'session.completed' | 'session.failed'" required>
Outcome kind.
</ParamField>

<ParamField body="output" type="string">
Required for `session.completed`.
</ParamField>

<ParamField body="error" type="JsonValue">
Error payload for `session.failed`.
</ParamField>

Returns `202` with `{ "ok": true }` on success, `404` when the callback is not pending.

---

## Dev-only routes

Mounted only during `eve dev`. `GET /eve/v1/info` reports `capabilities.devRoutes: true` when they are available.

:::endpoint GET /eve/v1/dev/runtime-artifacts
Returns the current dev runtime artifact revision token.
:::

Clients poll this to detect HMR rebuilds. When the revision changes, start new sessions against the fresh snapshot; in-flight sessions keep their original artifact binding.

<ResponseExample>

```json
{
  "revision": "<opaque-revision-id>"
}
```

</ResponseExample>

---

:::endpoint POST /eve/v1/dev/runtime-artifacts/rebuild
Flushes queued runtime artifact rebuilds, then returns the current revision (same shape as `GET`).
:::

---

:::endpoint POST /eve/v1/dev/schedules/:scheduleId
Dispatch one authored schedule exactly once without waiting for cron.
:::

`:scheduleId` is the filesystem-derived schedule name (e.g. `agent/schedules/heartbeat.ts` → `"heartbeat"`). URL-encode reserved characters.

<RequestExample>

```bash
curl -X POST http://127.0.0.1:3000/eve/v1/dev/schedules/heartbeat
```

</RequestExample>

<ResponseExample>

```json
{
  "scheduleId": "heartbeat",
  "sessionIds": ["sess_1", "sess_2"]
}
```

</ResponseExample>

Subscribe to `GET /eve/v1/session/:sessionId/stream` for each returned `sessionId`. Returns `404` with `availableScheduleIds` when the schedule name is unknown.

## Error responses

Most routes return JSON errors with `ok: false` and an `error` string. Common status codes:

| Status | Typical cause |
| --- | --- |
| `400` | Malformed body, missing required fields, invalid `startIndex` |
| `401` / `403` | Auth walk failed or forbidden |
| `404` | Unknown session, schedule, or expired callback token |
| `413` / `415` | Attachment exceeds size cap or violates media-type policy |
| `500` | Handler threw (`errorId` may be present for support correlation) |

## Client integration

`eve/client` wraps these routes with typed `Client` and `ClientSession` helpers, automatic stream reconnection, and continuation-token management. Framework plugins (`eve/next`, `eve/nuxt`, `eve/sveltekit`) can proxy same-origin. Prefer the client SDK over hand-rolling POST + NDJSON loops for scripts, tests, and custom UIs.

<Steps>
<Step title="Create and stream a session">

```bash
# 1. Create
RESP=$(curl -s -X POST http://127.0.0.1:3000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Hello"}')
SESSION_ID=$(echo "$RESP" | jq -r .sessionId)
TOKEN=$(echo "$RESP" | jq -r .continuationToken)

# 2. Stream until session.waiting
curl -N "http://127.0.0.1:3000/eve/v1/session/$SESSION_ID/stream"
```

</Step>
<Step title="Send a follow-up">

Wait for `session.waiting`, then continue:

```bash
curl -X POST "http://127.0.0.1:3000/eve/v1/session/$SESSION_ID" \
  -H 'content-type: application/json' \
  -d "{\"continuationToken\":\"$TOKEN\",\"message\":\"Shorter version, please.\"}"
```

</Step>
<Step title="Reconnect after disconnect">

```bash
curl -N "http://127.0.0.1:3000/eve/v1/session/$SESSION_ID/stream?startIndex=42"
```

</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Sessions and streaming" href="/sessions-and-streaming">
Continuation tokens, stream reconnect, subagent child-session attachment, and delivery ordering.
</Card>
<Card title="Client integration" href="/client-integration">
`eve/client`, streaming reducers, and React/Vue/Svelte hooks.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Route auth strategies, env vars, and production deployment verification.
</Card>
<Card title="Connections" href="/connections">
OAuth flows, connection callbacks, and `authorization.required` stream events.
</Card>
<Card title="Subagents and schedules" href="/subagents-and-schedules">
Local and remote subagents, cron schedules, and dev schedule dispatch.
</Card>
<Card title="Quickstart" href="/quickstart">
End-to-end flow from `eve init` through first HTTP session.
</Card>
</CardGroup>

---

## 24. Troubleshooting

> Discovery diagnostics from eve info and eve build, .eve/discovery/diagnostics.json, common failure modes, dev server PID conflicts, and runtime error codes from failed steps/turns.

- Page Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/pages/24-troubleshooting.md
- Generated: 2026-06-16T19:31:20.493Z

### Source Files

- `docs/guides/instrumentation.md`
- `docs/reference/cli.md`
- `packages/eve/src/cli/commands/info.ts`
- `packages/eve/src/compiler/artifacts.ts`
- `packages/eve/src/execution/runtime-errors.ts`
- `packages/eve/src/cli/dev/tui/setup-issues.ts`

---
title: "Troubleshooting"
description: "Discovery diagnostics from eve info and eve build, .eve/discovery/diagnostics.json, common failure modes, dev server PID conflicts, and runtime error codes from failed steps/turns."
---

Eve surfaces compile-time discovery problems through `eve info`, `eve build`, and `.eve/discovery/diagnostics.json`, and runtime failures through NDJSON stream events (`step.failed`, `turn.failed`, `session.failed`, or `session.waiting`). Start with discovery diagnostics before booting a dev server; for in-flight sessions, read the failure cascade and `code` field on the terminal boundary event.

## Discovery diagnostics workflow

Discovery runs on every compile. Eve writes artifacts under `.eve/` even when discovery produces errors, so you can inspect partial output after a failed build.

<Steps>
<Step title="Run eve info">

```bash
eve info
```

Or emit machine-readable output:

```bash
eve info --json
```

`eve info` reports compile status (`ready`, `failed`, or `unavailable`), error and warning counts, the active tools/skills/channels surface from the compiled manifest, messaging route paths, and artifact file paths.

</Step>
<Step title="Fix authored-shape errors">

Read each diagnostic's `message` and `sourcePath`. Errors block compile; warnings are printed at build time but do not fail the build.

</Step>
<Step title="Rebuild and verify">

```bash
eve build
```

When discovery errors remain, the CLI prints the full diagnostics report and the path to `.eve/discovery/diagnostics.json`. Re-run `eve info` to confirm `Compile: ready` and zero errors.

</Step>
</Steps>

<Note>
The recommended edit loop is: change files under `agent/` → `eve info` → `eve dev` → `eve build` → `eve start`. See the [CLI reference](/cli-reference) for command details.
</Note>

## Inspectable artifacts

Eve preserves discovery and compile artifacts under `.eve/`:

:::files
.eve/
├── discovery/
│   ├── agent-discovery-manifest.json   # what Eve found on disk
│   └── diagnostics.json                # authored-shape errors and warnings
└── compile/
    ├── compiled-agent-manifest.json    # serialized surface the runtime loads
    ├── compile-metadata.json           # build metadata, hashes, compile status
    └── module-map.mjs                  # compiled module entrypoints
:::

| Artifact | Path | Use when |
| --- | --- | --- |
| Discovery manifest | `.eve/discovery/agent-discovery-manifest.json` | Confirm a file was found on disk |
| Diagnostics | `.eve/discovery/diagnostics.json` | Read structured errors and warnings |
| Compiled manifest | `.eve/compile/compiled-agent-manifest.json` | See what the runtime actually serves |
| Compile metadata | `.eve/compile/compile-metadata.json` | Check `status` and diagnostic summary counts |
| Module map | `.eve/compile/module-map.mjs` | Trace compiled import entrypoints |

`eve info --json` includes an `artifacts` object with absolute paths to each file when compile state is available.

## diagnostics.json schema

Each diagnostics artifact is versioned and machine-readable:

<ResponseField name="kind" type="string">
Always `eve-discovery-diagnostics`.
</ResponseField>

<ResponseField name="version" type="number">
Schema version. Currently `1`.
</ResponseField>

<ResponseField name="summary" type="object">
`errors` and `warnings` counts. Build fails when `errors > 0`; `compile-metadata.json` records `status: "failed"` in that case.
</ResponseField>

<ResponseField name="diagnostics" type="array">
One entry per problem. Each diagnostic has:

- `code` — stable machine-readable identifier (for example `discover/required-instructions-missing`)
- `message` — human-readable explanation
- `severity` — `"error"` or `"warning"`
- `sourcePath` — absolute path to the offending file or directory
</ResponseField>

<ResponseExample>

```json
{
  "kind": "eve-discovery-diagnostics",
  "version": 1,
  "summary": { "errors": 1, "warnings": 0 },
  "diagnostics": [
    {
      "code": "discover/required-instructions-missing",
      "message": "No instructions prompt discovered.",
      "severity": "error",
      "sourcePath": "/path/to/agent"
    }
  ]
}
```

</ResponseExample>

When `eve build` fails, the CLI message includes the summary line, each diagnostic with severity and source path, and `Diagnostics artifact: <path>`.

Warnings are reported to stderr at build time:

```text
Warning [discover/deprecated-system-slot]: …
  source: /path/to/agent/system.md
```

## Discovery diagnostic codes

Diagnostic codes use the `discover/` prefix. The table below lists the stable codes emitted by the discovery compiler; the full set for your project is always in `.eve/discovery/diagnostics.json`.

| Code | Severity | Typical cause |
| --- | --- | --- |
| `discover/project-not-found` | error | No `agent/` directory resolved from the app root |
| `discover/required-instructions-missing` | error | Missing `instructions.md` or `instructions.ts` |
| `discover/deprecated-system-slot` | warning | Fallback to deprecated `system.{md,ts}` — rename to `instructions.*` |
| `discover/slot-collision` | error | Same slot has both markdown and module sources |
| `discover/module-slot-collision` | error | Multiple module sources compete for one slot |
| `discover/tool-name-invalid` | error | `agent/tools/<name>.ts` filename violates tool slug charset |
| `discover/connection-name-invalid` | error | Connection filename violates kebab-case slug rule |
| `discover/channel-name-invalid` | error | Channel filename or directory segment violates slug rule |
| `discover/hook-name-invalid` | error | Hook filename or directory segment violates slug rule |
| `discover/tools-directory-invalid` | error | `agent/tools/` exists but is not a directory |
| `discover/channels-directory-invalid` | error | `agent/channels/` exists but is not a directory |
| `discover/hooks-directory-invalid` | error | `agent/hooks/` exists but is not a directory |
| `discover/connections-directory-invalid` | error | `agent/connections/` exists but is not a directory |
| `discover/sandbox-directory-invalid` | error | `agent/sandbox/` exists but is not a directory |
| `discover/skills-directory-invalid` | error | `agent/skills/` exists but is not a directory |
| `discover/schedules-directory-invalid` | error | Root `schedules/` exists but is not a directory |
| `discover/unsupported-directory` | warning | Ignored directory under `agent/` |
| `discover/skill-collision` | error | Duplicate skill name |
| `discover/skill-markdown-missing` | error | Packaged skill directory missing `SKILL.md` |
| `discover/skill-frontmatter-invalid` | error | Invalid YAML frontmatter in a skill |
| `discover/connection-file-folder-collision` | error | Both file and folder exist for one connection name |
| `discover/connection-folder-empty` | error | Connection folder has no module source |
| `discover/sandbox-folder-empty` | error | Sandbox folder has no module source |
| `discover/required-subagent-config-module-missing` | error | Subagent missing required `agent.ts` |
| `discover/local-subagent-schedules-invalid` | error | Schedules under a subagent (root-only slot) |
| `discover/schedule-file-unsupported` | error | Unsupported file type under `schedules/` |

<Tip>
Tool names are derived from filenames under `agent/tools/` — there is no authored `name` field. If the model never sees a tool, confirm the file path and slug, then check diagnostics for shape errors. See [Project layout](/project-layout) and [Tools](/tools).
</Tip>

## Common failure modes

| Symptom | Likely cause and fix |
| --- | --- |
| Tool not discovered (model never sees it) | Run `eve info`. Confirm the file is in `agent/tools/<name>.ts` and default-exports `defineTool(...)`. Check `.eve/discovery/diagnostics.json`. Remember `schedules/` is root-only. |
| Model won't call a tool it should | Tighten the tool `description` and `inputSchema`; put procedural guidance in a skill, not the description. Confirm the tool appears in `eve info`. |
| Stuck on `session.waiting` | Turn is parked on approval, `input.requested`, or connection sign-in. Respond via the client, or POST a follow-up with the current `continuationToken`. A stale token is rejected. |
| `401` on production routes | Expected fail-closed auth. Replace `placeholderAuth()`, set `VERCEL_PROJECT_ID`, and configure environment so `vercelOidc()` accepts user tokens. See [Auth and deployment](/auth-and-deployment). |
| Build fails with discovery errors | Read the printed diagnostics and `.eve/discovery/diagnostics.json`. Validate root-vs-subagent boundaries and that secrets come from env vars, not authored files. |
| AI Gateway auth errors at runtime | Missing or stale `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN`. Run `eve link` or set credentials in `.env.local`. In `eve dev`, use `/model` to refresh. |
| `eve eval` exits `2` | Configuration error (missing evals, invalid flags, unreachable target). Exit `1` means a failed check or execution error; exit `0` means all passed. |

## Dev server PID conflicts

Local `eve dev` writes the active server process ID to `.eve/dev-process.pid`. Before starting, Eve checks whether that PID is still running.

<Warning>
If another `eve dev` is already running for the same agent, startup exits with:

```text
A dev server is already running for this Eve agent (pid 12345).
To stop it, run: kill 12345
```

On Windows the suggested command is `taskkill /PID <pid>`. The PID file is removed when the dev server shuts down cleanly.
</Warning>

### Port binding

When no explicit `--port` is given, Eve retries up to several consecutive ports if the default (`$PORT`, then `3000`) is in use (`EADDRINUSE`). With an explicit `--port`, a conflict throws immediately.

Manual cleanup after stopping `eve dev`:

- Delete stale runtime snapshots: `.eve/dev-runtime/snapshots/`
- Delete old local sandbox templates: `.eve/sandbox-cache/local/templates/`

Connect the TUI to a remote server instead of starting a local one:

```bash
eve dev https://your-app.vercel.app
# or
eve dev --url https://your-app.vercel.app
```

## TUI setup issues

The `eve dev` TUI runs cheap boot detections before the first prompt. Issues appear as an attention line (for example `1 setup issue: model provider not linked · /model`).

| Label | Fix command | When it fires |
| --- | --- | --- |
| `model provider not linked` | `/model` | No `.vercel/project.json` and no gateway credentials in env |
| `AI Gateway credentials missing` | `/model` | Project is linked but neither `AI_GATEWAY_API_KEY` nor `VERCEL_OIDC_TOKEN` is set |
| `not logged in` | `/login` | Off-critical-path `vercel whoami` probe reports logged out |
| `Vercel CLI not found` | `/vc` | `vercel` binary absent from `PATH` |

<Note>
External-provider models (routed outside the AI Gateway) skip gateway credential detections entirely — they use their own provider key and `/model` cannot reconfigure them.
</Note>

For gateway-auth `MODEL_CALL_FAILED` events, the TUI replaces generic remediation text with actionable lines (stale API key, expired OIDC token, or missing credentials).

## Runtime failure cascades

When a model call or turn fails, the harness emits a cascade of NDJSON events sharing the same `code` and `message`. The third event determines whether the session can continue.

```mermaid
stateDiagram-v2
  [*] --> TurnInProgress
  TurnInProgress --> TerminalFailure: emitFailedStep
  TurnInProgress --> RecoverableFailure: emitRecoverableFailedTurn
  TurnInProgress --> TurnComplete: success
  TerminalFailure --> SessionFailed: session.failed
  RecoverableFailure --> SessionWaiting: session.waiting
  TurnComplete --> SessionWaiting: conversation mode
  TurnComplete --> SessionCompleted: task mode
  SessionFailed --> [*]
  SessionWaiting --> TurnInProgress: follow-up with continuationToken
  SessionCompleted --> [*]
```

| Cascade | Events | Session state after |
| --- | --- | --- |
| Terminal | `step.failed` → `turn.failed` → `session.failed` | Dead — no further follow-up on the same continuation token |
| Recoverable | `step.failed` → `turn.failed` → `session.waiting` | Parked — send another message with the current `continuationToken` |
| Success (conversation) | `turn.completed` → `session.waiting` | Ready for next user message |
| Success (task) | `turn.completed` → `session.completed` | Terminal success |

`session.waiting` with `{ "wait": "next-user-message" }` is normal between turns. It also follows recoverable failures so you can retry after fixing credentials or transient provider errors.

Parked work that emits `session.waiting` without a failure cascade includes HITL approvals (`input.requested`), `ask_question`, and connection OAuth (`authorization.required`).

## Runtime error codes

Failure events carry `{ code, message, details? }`. The `code` is the stable identifier; `details` may include `errorId` (for log correlation), gateway metadata, or workflow-stream diagnostics.

### step.failed / turn.failed / session.failed codes

| Code | Terminal or recoverable | Meaning |
| --- | --- | --- |
| `MODEL_CALL_FAILED` | Both — terminal for config errors; recoverable for transient provider failures in conversation mode | Model call failed. Check `details` for `gatewayName`, `statusCode`, `errorId`. Gateway auth failures include `GatewayAuthenticationError` or summary name `AI Gateway authentication failed`. |
| `WORKFLOW_STREAM_WRITE_FAILED` | Recoverable | Durable event-stream flush to the workflow server failed (timeout, 5xx). `details` includes `operation`, `statusCode`, `url`, `vercelId`, `vercelError`. The model call may have succeeded. |
| `OUTPUT_SCHEMA_NOT_FULFILLED` | Terminal in task mode; recoverable in conversation mode | Agent could not produce output matching `outputSchema` / `final_output`. |
| `SESSION_FAILED` | Terminal | Session callback step failed. |
| `REMOTE_AGENT_START_FAILED` | Terminal | Remote agent dispatch could not start. |
| `REMOTE_AGENT_FAILED` | Terminal | Remote agent callback reported failure. |
| `SUBAGENT_EXECUTION_FAILED` | Surfaced on parent via `action.result` | Delegated subagent run failed; appears in subagent-result output, not always as a root-session `step.failed`. |

### Action and tool result codes

These appear on `action.result` events (and in tool-result message parts), not necessarily as `step.failed` codes:

| Code | Meaning |
| --- | --- |
| `TOOL_EXECUTION_DENIED` | HITL approval denied or tool execution blocked |
| `TOOL_EXECUTION_FAILED` | Tool returned an error-shaped JSON output (`{ code, message }`) |
| `ACTION_RESULT_FAILED` | Fallback when an action result is marked `isError` without a structured code |

### Continuation token errors

<ResponseField name="NO_ACTIVE_SESSION" type="runtime error code">
Thrown when `continuationToken` does not match an in-flight session. Callers using resume-or-start treat this as a signal to create a fresh session. Message: `No active session for continuationToken "<token>".`
</ResponseField>

### Failure event payload shape

<ResponseExample>

```json
{"type":"step.failed","data":{"code":"MODEL_CALL_FAILED","message":"AI Gateway rejected the OIDC token.","sequence":4,"stepIndex":1,"turnId":"turn_0","details":{"errorId":"err_abc","gatewayName":"GatewayAuthenticationError"}}}
{"type":"turn.failed","data":{"code":"MODEL_CALL_FAILED","message":"AI Gateway rejected the OIDC token.","sequence":4,"turnId":"turn_0","details":{"errorId":"err_abc","gatewayName":"GatewayAuthenticationError"}}}
{"type":"session.waiting","data":{"wait":"next-user-message"}}
```

</ResponseExample>

For terminal failures the cascade ends with `session.failed` instead of `session.waiting`. Clients should key on the boundary event and deduplicate the shared `code:message` pair across the cascade.

### Correlating with logs

Model-call and workflow-stream failures attach `errorId` in `details`. Grep server logs for that ID to find the structured dump. Unrecognized failures may also include `details.detail` with a full stack trace; recognized gateway and config failures omit the dump.

## Observability for runtime failures

Three surfaces help debug runtime issues:

| Surface | Where | What it shows |
| --- | --- | --- |
| NDJSON stream | `GET /eve/v1/session/:sessionId/stream` | Live `step.failed`, `turn.failed`, `session.failed`, tool events |
| Workflow run tags | Vercel Workflow dashboard (`$eve.*`) | Session/turn tree, model id, token counts — no `instrumentation.ts` required |
| OpenTelemetry | `agent/instrumentation.ts` | AI SDK spans when configured |

See [Instrumentation and evals](/instrumentation-and-evals) for tracing setup and workflow run tags.

## Related pages

<CardGroup>
<Card title="CLI reference" href="/cli-reference">
All `eve` commands, flags, exit codes, and the edit-info-dev-build-start loop.
</Card>
<Card title="Quickstart" href="/quickstart">
Scaffold, verify discovery with `eve info`, and iterate with `eve dev`.
</Card>
<Card title="Sessions and streaming" href="/sessions-and-streaming">
`continuationToken` contracts, NDJSON events, and reconnect behavior.
</Card>
<Card title="HTTP API" href="/http-api">
Stable `/eve/v1` routes and stream event vocabulary.
</Card>
<Card title="Auth and deployment" href="/auth-and-deployment">
Route auth, env vars, and production verification.
</Card>
<Card title="Project layout" href="/project-layout">
Authored slots, path-derived naming, and what compiles into `.eve/`.
</Card>
</CardGroup>

---