@sashimikun_void: Vercel released Eve today. My first instinct wasn't to clone the repo. It was to generate a wiki and start asking quest…

X AI KOLs Following Tools

Summary

Vercel released Eve, an open-source filesystem-first durable backend agent framework, with comprehensive documentation available.

Vercel released Eve today. My first instinct wasn't to clone the repo. It was to generate a wiki and start asking questions. https://t.co/WDv6yLkDLO
Original Article
View Cached Full Text

Cached at: 06/16/26, 11:41 PM

Vercel released Eve today.

My first instinct wasn’t to clone the repo.

It was to generate a wiki and start asking questions.

https://t.co/WDv6yLkDLO


Eve Documentation

Source: https://grok-wiki.com/public/docs/vercel-eve-759e1d74a10f Agent-readable docs

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

Full Markdownllms.txtMarkdown aliasGitHub## 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://grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/llms.txt)
- [Human interactive docs](https://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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://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/)

:::

PathResolves toAuthor with
agent/tools/get_weather.tstool get_weatherdefineTool from eve/tools
agent/connections/linear.tsconnection lineardefineMcpClientConnection or defineOpenAPIConnection from eve/connections
agent/skills/summarize.mdskill summarizemarkdown file (loaded via load_skill)
agent/channels/slack.tschannel slackdefineChannel or platform factory from eve/channels/*
agent/subagents/researcher/subagent researchernested 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.

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.

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.

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
LayerResponsibilityOwns
ChannelNormalize inbound transport, apply auth and delivery policy, start or resume sessionscontinuationToken
HarnessRun one unit of AI work (model step, tool calls, compaction) and return { session, next }Turn/step execution inside the agent loop
RuntimePersist session state, follow next, stream events, orchestrate workflow primitivessessionId, 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.

CommandPurpose
eve init [target]Scaffold a new agent or add one to an existing project
eve infoPrint discovered tools, skills, routes, artifacts, and diagnostics
eve buildCompile .eve/ artifacts and build host output
eve startServe the built .output/ app
eve devStart local runtime and open the interactive terminal UI
eve link / eve deployLink to Vercel and deploy
eve evalRun evals locally or against a remote URL
eve channels add/listScaffold or list authored channels

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

/eve/v1 HTTP routes

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

MethodRoutePurpose
GET/eve/v1/healthHealth check
GET/eve/v1/infoJSON inspection payload for the current agent
POST/eve/v1/sessionCreate a new durable session
POST/eve/v1/session/:sessionIdContinue a session with continuationToken
GET/eve/v1/session/:sessionId/streamNDJSON event stream
GET/eve/v1/connections/:name/callback/:tokenOAuth callback (unguessable token)
GET/eve/v1/callback/:tokenTerminal 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 pathHelperAuthored slot
evedefineAgent, defineRemoteAgentagent/agent.ts
eve/instructionsdefineInstructions, defineDynamicagent/instructions.ts or agent/instructions/
eve/toolsdefineTool, defineBashTool, defineReadFileTool, …agent/tools/*.ts
eve/skillsdefineDynamicdynamic skill modules
eve/connectionsdefineMcpClientConnection, defineOpenAPIConnectionagent/connections/*.ts
eve/channelsdefineChannelagent/channels/*.ts
eve/sandboxdefineSandboxagent/sandbox.ts or agent/sandbox/sandbox.ts
eve/schedulesdefineScheduleagent/schedules/*.ts or *.md
eve/hooksdefineHookagent/hooks/*.ts
eve/contextdefineStatesession-scoped durable state
eve/instrumentationdefineInstrumentationagent/instrumentation.ts (root-only)
eve/evalsdefineEval, defineEvalConfigevals/ 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

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.

cd my-agent
npx eve info

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

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/...).

```bash curl -X POST http://127.0.0.1:2000/eve/v1/session \ -H 'content-type: application/json' \ -d '{"message":"Hello"}' ``` ```json { "sessionId": "ses_…", "continuationToken": "ct_…" } ```

Attach to the stream:

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.

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

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

Compiled artifacts

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

ArtifactContents
.eve/discovery/agent-discovery-manifest.jsonFilesystem discovery results
.eve/discovery/diagnostics.jsonAuthored-shape errors and warnings
.eve/compile/compiled-agent-manifest.jsonSerialized surface Eve loads at runtime
.eve/compile/module-map.mjsCompiled module entrypoints

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

Next

Node 24+ prerequisites, install paths, model credentials, and environment file loading. Scaffold, verify with `eve info`, iterate with `eve dev`, and exercise `/eve/v1/session`. Every authored slot under `agent/`, path-derived naming, and what compiles into `.eve/`. Session/turn/step nesting, durable checkpoints, and parked work semantics. `continuationToken` vs `sessionId`, NDJSON events, and reconnect behavior. All `eve` commands, flags, and the edit-info-dev-build-start loop.

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://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

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.

nvm install 24
nvm use 24
node --version
fnm install 24
fnm use 24
node --version

Download Node 24+ from nodejs.org or use your system package manager, then verify:

node --version

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 below.

Install paths

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

npx runs eve init without installing Eve globally first:

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.

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

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.

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

{
  "engines": {
    "node": "24.x"
  }
}
```bash title="npm" npm install eve@latest ai zod ```
pnpm add eve@latest ai zod
yarn add eve@latest ai zod
bun add eve@latest ai zod

Then author the two files the runtime needs:

You are a concise assistant. Use tools when they are available.
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.

What gets installed

A scaffolded agent declares these runtime dependencies:

PackageRole
eveFramework, CLI binary, compiled runtime
aiAI SDK model calls and streaming
zodTool and schema validation
@vercel/connectConnection 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:

Maps to `./bin/eve.js` in the published `eve` package. Running `eve` with no subcommand runs `eve dev`.

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:

{
  "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:

CredentialEnvironment variableHow to obtain
API keyAI_GATEWAY_API_KEYCreate a key in the Vercel AI Gateway dashboard, or paste one in the dev TUI /model flow
OIDC tokenVERCEL_OIDC_TOKENRun 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.

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

AI_GATEWAY_API_KEY=your-gateway-key

From an interactive terminal in your agent project:

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.

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 patternEnvironment 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.6ANTHROPIC_API_KEY).

To use a direct provider SDK model instead of a gateway string id, change 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

node --version

Expect v24.x or newer.

npx eve --version

Prints the installed eve package version.

From your agent project root:

npx eve info

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

npm run dev

Or, without a dev script:

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.

Common issues

SymptomLikely causeFix
Eve requires Node.js >=24Node version too oldInstall Node 24+ and retry
Gateway “no credentials” errorMissing AI_GATEWAY_API_KEY and VERCEL_OIDC_TOKENRun eve link or set AI_GATEWAY_API_KEY in .env.local
Stale gateway key shadows OIDCShell exports an old AI_GATEWAY_API_KEYUnset the shell variable or update the key
Provider key missing for direct modelExternal provider without env varSet the provider’s *_API_KEY in .env.local
Env changes not picked upRunning outside eve devRestart the process, or use eve dev which hot-reloads env files

For deeper diagnostics, see Troubleshooting.

Next

Scaffold with `eve init`, verify with `eve info`, iterate with `eve dev`, and exercise the HTTP session API. Where authored files live under `agent/` and what compiles into `.eve/` artifacts. Configure `defineAgent` model, compaction, and build options in `agent/agent.ts`. Route auth, Vercel secrets, `eve link`/`eve deploy`, and production verification. Full command reference for `init`, `info`, `build`, `start`, `dev`, `link`, `deploy`, and `eval`.

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://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

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

See [Installation](/installation) for Node version requirements, package-manager install paths, and model credential setup.
  • 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

Run eve init without a global install:

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.

`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.

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

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.

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/:

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:

LabelExample value
CreatePOST /eve/v1/session
ContinuePOST /eve/v1/session/:sessionId
StreamGET /eve/v1/session/:sessionId/stream

For machine-readable output:

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.

A healthy quickstart run shows compile status `ready` and zero discovery errors.

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:

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:

npm run dev

Equivalent direct invocation:

npx eve dev
Listen port. Defaults to `$PORT`, then `2000`. Start the server without the interactive terminal UI. Connect the TUI to an existing server URL instead of starting a local host (for example, `eve dev https://your-app.vercel.app`).

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.

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

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. :::

Plain text or multimodal AI SDK user content. One-turn client context converted into model context by the channel. JSON schema for structured assistant output on this turn.
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?"}'
{
  "continuationToken": "eve:…",
  "ok": true,
  "sessionId": "session_…"
}

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

Namespaced token required to continue the conversation. Store it with `sessionId`. Durable session identifier for stream attachment and follow-up posts.

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

Stream the session

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

Non-negative event index to resume from. Omit to receive the full stream from the beginning.
curl -N http://127.0.0.1:2000/eve/v1/session/<sessionId>/stream

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:

EventWhen
session.startedWorkflow session begins
turn.startedTurn boundary
message.receivedUser message accepted
actions.requestedHarness requests tool calls (for example get_weather)
action.resultTool result projected back
message.completedFinal assistant text for the step
session.waitingSession parked for the next user message
session.completedTerminal 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.

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. :::

Token from the create response (or the latest turn boundary). Requests without it return `400`. Next user message. Required unless `inputResponses` is supplied. Human-in-the-loop responses when the harness emitted `input.requested`.
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."}'
{
  "ok": true,
  "sessionId": "session_…"
}

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

SurfaceRole
agent/instructions.mdAlways-on behavior
agent/tools/*.tsTyped capabilities the model invokes
agent/channels/eve.tsHTTP ingress for sessions
eve devLocal 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

Authored slots under `agent/`, path-derived naming, and what compiles into `.eve/`. `continuationToken` contracts, NDJSON vocabulary, reconnect behavior, and subagent streams. Built-in tools, compaction defaults, and override patterns. `eve/client` `Client` and `ClientSession`, plus React/Vue/Svelte hooks. Full `/eve/v1` route inventory, request shapes, and error codes. Discovery diagnostics, dev-server conflicts, and runtime failure codes.

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://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

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:

RootRole
App rootThe enclosing project directory (marked by package.json or vercel.json)
Agent rootThe directory Eve walks for authored slots

Eve supports two layouts:

LayoutAgent rootApp rootWhen to use
Nested (recommended)<app-root>/agent/Parent of agent/Keeps app scaffolding separate from the authored surface
FlatSame as app rootSame directorySupported 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.

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

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 pathRuntime identifier
agent/tools/get_weather.tstool get_weather
agent/tools/billing/refund.tstool billing-refund (nested segments flattened with -)
agent/connections/linear.tsconnection linear
agent/skills/summarize.mdskill summarize
agent/subagents/researcher/agent.tssubagent 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.

PathDescriptionSubagentsNotes
agent.tsRuntime config (defineAgent)YesModel, compaction, build.externalDependencies, experimental. Required description on subagents.
instructions.md / instructions.ts / instructions/Base system promptOptionalFlat 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.tsTelemetry configNoOTel exporter and AI SDK span settings. Auto-discovered at server startup before agent code. Root-only.
channels/HTTP and messaging entrypointsNoModule-backed only. Recursive directories supported. Root-only.
connections/MCP and OpenAPI connectionsYesOne connection per file or folder (connections/<name>/connection.ts). Name derived from path segment.
hooks/Lifecycle and stream-event subscribersYesModule-backed only. Recursive directories supported.
skills/On-demand procedures and capability packsYesFlat markdown, module-backed skills, or packaged skills. Seeded into /workspace/skills/....
lib/Shared authored helper codeYesImport-only; never mounted into the sandbox workspace.
sandbox.ts or sandbox/sandbox.tsSingle sandbox definitionYesTop-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 sandboxYesMirrored into /workspace/... at session bootstrap. Cannot contain skills/ (reserved).
tools/Typed executable integrationsYesModule-backed only. Recursive directories supported; nested paths flatten into tool names.
schedules/Recurring jobsNo<name>.ts (defineSchedule) or <name>.md (frontmatter cron: + prompt body). Recursive nesting supported. Root-only.
subagents/Specialist child agentsYesEach child is a local package under subagents/<id>/, a single-file module, or a defineRemoteAgent module. Nested subagents supported.
Legacy `system.md` / `system.ts` still resolve with a deprecation warning. Prefer `instructions.*`.

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 sourceSandbox 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.

SlotDeclared subagent behavior
InstructionsOwn instructions.{md,ts} or directory; optional
Tools, connections, skills, hooksOwn directories only
SandboxOwn sandbox.ts or sandbox/; else framework default (not parent’s sandbox)
Channels, schedules, instrumentationNot 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 patternPurpose
evals/**/*.eval.tsEval definitions (defineEval default export)
evals/evals.config.tsRequired 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.

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/)

FileContents
agent-discovery-manifest.jsonRaw discovery manifest: source refs, logical paths, subagent graph before module import
diagnostics.jsonStructured discovery diagnostics with error and warning counts

Compile artifacts (.eve/compile/)

FileContents
compiled-agent-manifest.jsonVersioned 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.mjsCompiled module map for loading authored exports at runtime
compile-metadata.jsonGenerator 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.tsGenerated channel instrumentation types
When discovery reports errors, `compile-metadata.json` sets `status` to `failed` and `eve build` throws. Warnings alone still produce artifacts with `status: ready`.

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

eve info

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

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

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

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

Filesystem-first agent model, channel/harness/runtime split, and the shortest path from init to a running session. Scaffold with `eve init`, verify with `eve info`, iterate with `eve dev`. Local subagents, remote agents, cron schedules, and delegation boundaries. `defineInstrumentation`, `defineEval`, `eve eval`, and reporters. All `eve` commands, flags, `.eve/` artifact paths, and the edit-info-dev-build-start loop. Discovery diagnostics, common failure modes, and runtime error codes.

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://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

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.

LevelScopeRuntime ownerTypical stream events
SessionWhole durable conversation or taskworkflowEntry driver workflowsession.started, session.waiting, session.completed, session.failed
TurnOne inbound delivery and all work it triggers until the agent respondsturnWorkflow child workflowturn.started, turn.completed, turn.failed
StepOne model call and the tool calls it makesturnStep ("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.

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.

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
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()`. 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. Atomic harness step inside `"use step"`. Reads the durable session, runs one tool-loop iteration, writes an updated `DurableSessionState` snapshot into the step result. Workflow primitives (`start()`, `resumeHook()`, `createHook()`) are implementation details of Eve's runtime layer. Channels, tools, and hooks never call them directly.

Durable checkpoints and wire migrations

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

Workflow run id for the session. Hook token clients use to resume the parked session. May differ from `sessionId` when a channel re-keys mid-session. 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. Closed-contract short-circuit. When `false`, the driver skips per-delivery proxy-routing steps for descendant HITL. `{ turnId, sequence, stepIndex }` for stamping protocol events without re-reading the full session in the workflow body.

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.

**Completed steps never re-run.** The Workflow SDK replays recorded step results at boundaries. **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.

After a recoverable step or turn failure, Eve emits step.failedturn.failedsession.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.

kindWhen emittedDriver behavior
doneTurn produced terminal outputFire session callback, notify delegated parent, exit driver
parkTurn waiting on human input, OAuth, or adapter-only deliveryRegister park hook, emit session.waiting, block until resumeHook
dispatch-runtime-actionsPending subagent or remote-agent callsStart child sessions, wait for runtime-action-result deliveries
dispatch-code-mode-runtime-actionsCode-mode runtime action interruptDispatch 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 === truepark
  • 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.

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.

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). 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.

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.

SituationBehavior
Session parked (session.waiting)One delivery to continuationToken wakes the session and starts the next turn
Turn already activeThe hook may accept additional deliveries, but the driver drains them only at specific workflow boundaries
Multiple deliveries ready at drain timeEve may coalesce them into one turn (coalesceDeliveries); drain is best-effort and timing-dependent
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.

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

Turn epilogue emits `turn.completed` then `session.waiting`. Session parks between turns indefinitely. Turn epilogue emits `turn.completed` then `session.completed`. Cannot park on empty follow-up input. Used for subagent delegation, schedules, and function-output contracts.

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

`continuationToken` vs `sessionId`, NDJSON events, reconnect with `startIndex`, and subagent child-session attachment. HITL approval predicates, `requireAuth`, and tool execution inside durable steps. Local subagents, remote agents, cron schedules, and task-mode execution constraints. `defineState` persistence, `defineHook` subscribers, and managed-context APIs. OAuth callback routes and authorization parking semantics.

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://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

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

HandleRoleUsed for
continuationTokenResume handle for the session’s current workflow hookPOST follow-ups (message, inputResponses) while the session is parked or active
sessionIdStream-and-inspect handle (workflow run id)GET /eve/v1/session/:sessionId/stream, x-eve-session-id response header
Do not interchange these handles. `continuationToken` is owned by the channel and namespaced internally as `:` (for example `eve:` on the default HTTP channel). `sessionId` is runtime-owned and stable for the durable run.

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.

MethodPathStatusPurpose
POST/eve/v1/session202Create a session and start the first turn
POST/eve/v1/session/:sessionId200Continue a session with continuationToken
GET/eve/v1/session/:sessionId/stream200Stream NDJSON lifecycle events
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.

Plain text or an AI SDK `UserContent` array (`text` and `file` parts). Required on create. One-turn client/page context. Strings become user-role context messages prefixed with `Client context:\n`. Not persisted to durable history. JSON Schema the turn result must satisfy before the turn terminates. Run mode. `conversation` enables recoverable input requests; `task` fails when input would be required. Optional terminal callback metadata. The runtime posts once when the session completes or fails. Channel-local resume token (raw form, without the `eve:` prefix in the JSON body). Durable session id for streaming. Same value as `sessionId`. Present on create and continue responses.
curl -X POST http://127.0.0.1:3000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Summarize the latest forecast."}'
{
  "continuationToken": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "ok": true,
  "sessionId": "wrun_abc123"
}
:::

:::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.

Existing session id. Must resolve via `getSession`; otherwise `404`. Current resume token. Missing or empty → `400`. Follow-up user message. At least one of `message` or `inputResponses` is required. HITL responses resolving pending `input.requested` batches (tool approvals, `ask_question` answers). Same semantics as create; applies to this delivery only. Per-turn structured output schema override.
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."}'
{
  "ok": true,
  "sessionId": "wrun_abc123"
}
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. :::

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

Session to stream. Unknown id → `404`. 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`. `application/x-ndjson; charset=utf-8` `ndjson` `15` (current stream schema version) Echoes the requested `sessionId`.
curl "http://127.0.0.1:3000/eve/v1/session/wrun_abc123/stream?startIndex=42"
:::

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:

EventMeaning
session.waitingSession parked; safe to send the next follow-up with the current continuationToken
session.completedTerminal success
session.failedTerminal 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

EventMeaning
session.startedDurable session created; child runs include data.invocation (kind: "subagent", parent ids)
turn.startedNew turn; data.turnId, data.sequence
message.receivedInbound user message accepted
step.startedModel step began
actions.requestedTool call batch requested (data.actions)
action.resultTool result projected (data.status: completed | failed)
input.requestedHITL pause; data.requests
step.completedModel step finished; data.finishReason, optional data.usage
step.failedStep failure; { code, message, details? }
turn.completedTurn succeeded
turn.failedTurn failure; { code, message, details? }
result.completedStructured output for schema turns; data.result
compaction.requested / compaction.completedContext compaction checkpoint
authorization.required / authorization.completedConnection OAuth challenge and outcome

Streaming text and reasoning

EventMeaning
message.appendedAssistant text delta; messageDelta + cumulative messageSoFar
message.completedFinalized assistant text block; data.finishReason
reasoning.appendedReasoning delta; reasoningDelta + reasoningSoFar
reasoning.completedFinalized 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

EventMeaning
subagent.calledWorkflow subagent delegated; includes data.childSessionId for child stream attachment
subagent.startedInline subagent execution began
subagent.eventWraps one child stream event under data.event (inline path)
subagent.completedInline 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.

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.

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:

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:

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

Session/turn/step nesting, parked work, and continuationToken delivery constraints. Full route inventory, error shapes, and NDJSON vocabulary. `Client`, `ClientSession`, streaming reducers, and framework hooks. Local subagents, remote agents, and delegation boundaries.

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://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

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.

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
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.

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.

SettingDefaultBehavior
compaction.thresholdPercent0.9Fraction of the primary model context window that triggers summarization
Recent window10 messagesHarness keeps the tail of history verbatim; older messages are summarized
Compaction summary modelActive turn modelOverride with compaction.model to use a different model for summaries only
Fallback threshold100_000 tokensUsed 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:

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.

ToolPurposeWhere it runs
bashRun a shell command in the workspaceSandbox (proxied via executeBashOnSandbox)
read_fileRead a text file with line-numbered output; records read stampsSandbox FS
write_fileWrite a complete file; enforces read-before-write and stale-read detectionSandbox FS
globFind files by glob patternSandbox FS
grepSearch file contents by regexSandbox FS
web_fetchFetch a URL (markdown/text/html)App runtime
web_searchSearch the web via the model providerProvider (no local execute; injected per step)
todoMaintain a durable per-session todo listApp runtime (TodoStateKey)
ask_questionAsk the user a clarifying question mid-turn; parks until answeredApp runtime (client-side, no execute)
agentDelegate a subtask to a copy of the current agentApp runtime (subagent-call runtime action)
load_skillPull on-demand skill instructions into the current turnApp runtime (reads SKILL.md from sandbox)
connection__searchDiscover tools across declared connectionsApp 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):

{
  "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.

The model-facing search tool is `connection__search` (double-underscore namespace), not `connection_search`. Qualified connection tools use the pattern `connection____`.

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:

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.

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.

Disable a default

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

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

GoalAction
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)DisabledisableTool() sentinel
New capability the harness does not shipAuthor — 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:

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:

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.

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).

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

Session/turn/step nesting, durable checkpoints, crash resume, and parked work semantics. `defineTool`, HITL approval, `toModelOutput`, and dynamic tool resolution beyond built-ins. Sandbox backends, workspace seeding, and how shell/file tools proxy execution. `defineAgent` fields including `compaction`, `modelOptions`, and `experimental.codeMode`. Instructions vs skills, workspace visibility, and subagent context isolation.

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://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

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

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/)
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.

Always-on instructions

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

SourceWhen resolvedRuntime behavior
agent/instructions.mdBuild timeMarkdown captured into compiled manifest
agent/instructions.ts with defineInstructionsBuild time (once)Module runs once at compile; resulting markdown is frozen in manifest
agent/instructions/ directoryBuild timeNon-recursive; entries compose in filename order after any root instructions.md
agent/instructions/*.ts with defineDynamicRuntimeResolver 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.

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:

- 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.

Skill name or id from the Available skills block.

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

Skill authoring shapes

ShapePathSeeded to workspace
Flat markdownagent/skills/<name>.md/workspace/skills/<name>/SKILL.md
Packaged directoryagent/skills/<name>/SKILL.md + siblingsFull directory under /workspace/skills/<name>/
Module-backedagent/skills/<name>.ts with defineSkillGenerated SKILL.md and files entries
Dynamicagent/skills/<name>.ts with defineDynamicResolved 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.

ToolSandbox scopeTypical use
bash/workspace cwdls, find, rg, shell commands
read_fileSandbox FSLine-numbered text reads
globSandbox FSPattern-based file discovery
grepSandbox FSRegex search across files
write_fileSandbox FSFile writes (read-before-write enforced)
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.

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:

CapabilityImportAuthored under
InstructionsdefineDynamic, defineInstructions from eve/instructionsagent/instructions/
SkillsdefineDynamic, defineSkill from eve/skillsagent/skills/
ToolsdefineDynamic, defineTool from eve/toolsagent/tools/

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

Resolver events

EventDynamic instructionsDynamic skillsDynamic tools
session.startedYesYesYes
turn.startedYesYesYes
step.startedNoNoYes

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

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

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.

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.

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.

SlotBuilt-in agent toolDeclared subagent
InstructionsCopy of parentOwn instructions.{md,ts}, optional
ToolsInheritedOwn tools/
ConnectionsInheritedOwn connections/
SkillsInheritedOwn skills/ (invisible to parent)
SandboxShared with parentOwn sandbox/ or framework default
HooksInheritedOwn hooks/ (parent hooks do not fire)
StateFreshFresh
HistoryFresh child sessionFresh 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

NeedUse
Permanent identity and standing rulesinstructions.md / instructions.ts
Build-time prompt composition from typed helpersinstructions.ts with defineInstructions
Optional procedures that should not bloat every turnagent/skills/ + load_skill
Per-caller instructions or skill setsdefineDynamic in agent/instructions/ or agent/skills/
Per-session or per-step tool setsdefineDynamic in agent/tools/
File inspection or command executionSandbox tools against /workspace
Different prompt, tools, or sandbox for a subtaskDeclared subagent under agent/subagents/<id>/
Parallel work on the same files with same toolsBuilt-in agent tool
Long sessions exceeding context windowcompaction.thresholdPercent in agent.ts

Prompt assembly reference

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

  1. InstructionsInstructions (<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

Author always-on instructions and on-demand skills, including `load_skill` activation and workspace seeding. Built-in tools including `load_skill`, `bash`, and file ops that inspect the workspace. Workspace seeding, sandbox backends, and proxy execution for shell and file tools. Declared subagents, the built-in `agent` tool, and delegation boundaries. `ctx.session`, `ctx.getSkill`, and where managed-context APIs are valid. Compaction `thresholdPercent` and other settings that affect long-session context.

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://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

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.

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

CapabilityApp runtimeSandbox
process.env / secretsYesNo
Author Node.js code (defineTool, connections, state)YesNo
NetworkUnrestricted (host policy)Controlled by SandboxNetworkPolicy
FilesystemApp-owned pathsIsolated /workspace

On Vercel, the app runtime is a Vercel Function; each sandbox is a Vercel 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.

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

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:

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.

BackendsetNetworkPolicy 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 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:

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. Throws `ConnectionAuthorizationRequiredError` to trigger the same consent flow before any outbound call runs.

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

ChannelMechanismReplay protection
SlackHMAC-SHA256 over v0:{timestamp}:{body} (X-Slack-Signature)5-minute timestamp skew
GitHubHMAC-SHA256 over raw body (X-Hub-Signature-256)
TwilioHMAC-SHA1 over URL + sorted form params (X-Twilio-Signature)
TelegramConstant-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

Two rules apply to every channel — built-in and custom:
  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:

RouteAuth required
POST /eve/v1/sessionYes
POST /eve/v1/session/:sessionIdYes
GET /eve/v1/session/:sessionId/streamYes
GET /eve/v1/healthNo — 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 valueEffect
SessionAuthContextAccept request; halt walk
null / undefinedSkip to next entry
Throw UnauthenticatedError / ForbiddenErrorReject with structured 401 / 403

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

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

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

Shipped verifiers

HelperUse 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
`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.

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:

FieldMeaning
ctx.session.auth.currentCaller on the active inbound turn
ctx.session.auth.initiatorCaller 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

Swap `placeholderAuth()` in `agent/channels/eve.ts` for a real `AuthFn` (`vercelOidc()`, `httpBasic()`, `oidc()`, or your own). Verify an unauthenticated production request returns `401`. 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. Never embed secrets in compiled artifacts or sandbox workspace files. Route privileged calls through tools, connections, or credential brokering. Grant connections and tool `auth` strategies the least privilege required. Tokens reach external hosts on the trusted side, never the model. 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. Model- or user-controlled strings rendered into channel UIs should be escaped for that surface.

Related pages

Route auth walk, env vars and secrets, `eve link` / `eve deploy`, and production verification. `defineChannel`, platform factories, webhook verification, and `eve channels` scaffolding. MCP and OpenAPI connections, OAuth callbacks, and `getToken` / `requireAuth` flows. `defineSandbox` backends, network policy, workspace seeding, and proxy execution. `defineTool` auth, HITL approval, and `requireAuth` on individual tools.

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://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

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

SurfaceAuthored pathWhen it enters contextUse for
Instructionsagent/instructions.md, .ts, or instructions/Every turn, prepended to the system promptPermanent identity, tone, standing rules
Skillsagent/skills/*On demand via load_skill tool resultLong or situational procedures
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.

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

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:

import { defineInstructions } from "eve/instructions";
import { buildInstructionsPrompt } from "./lib/prompts.js";

export default defineInstructions({
  markdown: buildInstructionsPrompt(),
});
Resolved prompt text. The only field on `InstructionsDefinition`.

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.

---
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.

---
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:

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",
  },
});
Routing hint advertised in the Available skills block. Write it as the task that should trigger activation. Procedure body returned by `load_skill` (frontmatter stripped). Optional package-relative sibling files. Eve writes each entry under `/workspace/skills//` at compile and session bootstrap.

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.

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
Available skill name or id. Choose from the Available skills block.

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.

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.

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 shapeSandbox 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
Authoring `agent/sandbox/workspace/skills/...` is rejected at compile time. Eve manages the `skills/` workspace entry; put skill content under `agent/skills/` instead.

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):

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:

CodeCause
discover/required-instructions-missingRoot agent has no instructions.md, .ts, or instructions/ directory
discover/slot-collisionBoth instructions.md and instructions.ts at the agent root
discover/skill-collisionConflicting sources for the same skill id (e.g. foo.md and foo/ directory)
discover/skill-markdown-missingPackaged skill directory lacks SKILL.md
discover/skill-frontmatter-invalidInvalid YAML frontmatter or missing required description on packaged SKILL.md

Recommended layout

Keep `agent/instructions.md` (or `.ts`) focused on rules that apply every turn. Move long procedures out. Author flat `.md` skills for simple cases; use packaged directories when you need `references/`, `assets/`, or `scripts/`. Run `eve info` and confirm skills appear with sensible descriptions and no collision diagnostics. Run `eve dev`, send a request that matches a skill description, and confirm the model calls `load_skill` before following the procedure.

Related pages

Always-on instructions vs on-demand skills, workspace visibility, and dynamic capabilities. Built-in `load_skill` tool, compaction defaults, and framework tool overrides. Authored slots under `agent/`, path-derived naming, and what compiles into `.eve/`. Workspace seeding, sandbox backends, and bootstrap lifecycle. `ctx.getSkill`, `ctx.getSandbox`, and where managed-context APIs are valid. Typed executable integrations — the counterpart to instruction-only skills.

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://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

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. 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.

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

Model-facing summary of what the tool does. 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`. The implementation. May be sync or async. 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. Optional human-in-the-loop gate. See [Human-in-the-loop approval](#human-in-the-loop-approval). Optional outbound authorization strategy. See [Tool auth](#tool-auth). Optional projection controlling what the model sees as the tool result. See [Shape model output](#shape-model-output).

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

The ctx parameter

execute receives a ToolContextSessionContext plus token accessors when auth is declared.

MemberPurpose
ctx.session.idActive session identifier
ctx.session.authCaller snapshot from route auth (current, initiator)
ctx.session.turnActive turn metadata
ctx.session.parentParent 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 for the full context model.

Human-in-the-loop approval

Gate sensitive tools with needsApproval and the helpers from eve/tools/approval:

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

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 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:

toModelOutput(output) {
  return { type: "text", value: `Report for ${output.domain}: score ${output.score}.` };
},
`text` for a string summary; `json` for a smaller object. The model-facing payload.

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
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 and 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 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):

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:

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:

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 for how dynamic capabilities fit the broader model.

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 shapeFileTool name(s)
Single defineTool(...)agent/tools/analytics.tsanalytics
Map { export, query }agent/tools/tenant.tstenant__export, tenant__query
nullanyContributes 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

EventResolver runsTools available for
session.startedOnce per sessionEvery model call in the session
turn.startedOnce per turnEvery model call in the turn
step.startedBefore each model callThat 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

PhaseBehavior
Discovery / eve buildCompiles descriptors; never runs execute
Model sees descriptorsTool names, descriptions, and input schemas only
Tool call executesexecute runs in the app runtime
Step completesResult is recorded durably
Resume / replayCompleted 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:

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

Built-in tools every agent ships with, and override or disable patterns. Dynamic capabilities, skills, and subagent context isolation. MCP and OpenAPI tools with qualified names and connection-level auth. Full `ctx` model, `defineState`, and `defineHook` stream subscribers. `input.requested` events, `inputResponses`, and reconnect behavior. App runtime vs sandbox trust boundaries and secret brokering. Path-derived naming rules and what compiles into `.eve/` artifacts. Exported `define*` helpers, approval predicates, and built-in defaults.

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://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

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).

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.

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.

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

MCP connections

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

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

FieldPurpose
urlMCP server HTTP endpoint (Streamable HTTP or SSE).
descriptionModel-facing summary of the connection and its tools.
authOptional authorization strategy (see Authorization).
headersOptional extra HTTP headers; stacks with auth.
toolsOptional filter: exactly one of allow or block on remote tool names.
approvalOptional 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.

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

FieldPurpose
specHTTPS URL fetched at runtime, or an inline parsed object.
baseUrlOptional override for the document’s first usable servers entry.
operationsFilter 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

Declares whether the connection acts as the agent (`"app"`) or on behalf of the end user (`"user"`).
  • "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:

{ 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.

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.

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

FieldPurpose
urlAuthorize URL for redirect or device flows.
userCodeDevice code for device flows.
instructionsCall to action when no URL is available.
displayNameProvider 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:

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. :::

Path-derived connection name (for example `linear`). Unguessable workflow hook capability minted by `getHookUrl`. Authorizes the resume; the route is intentionally unauthenticated.

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.

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 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

SymptomLikely cause
connection__search missingAgent has no files under agent/connections/.
Connection in prompt but search returns errorsRemote 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 loopToken rejected immediately after completeAuthorization; runtime fails with token_rejected_after_authorization.
principal_requiredprincipalType: "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

Where `agent/connections/` fits in the authored agent tree and path-derived naming rules. Built-in `connection__search` registration and how discovered tools join the toolset. Authored tools, approval predicates, and per-tool `auth` with `getToken` / `requireAuth`. Turn parking, durable steps, and resume semantics for OAuth and HITL. Stable `/eve/v1` routes including connection OAuth callbacks. Secret brokering, token handling, and trust boundaries for connections. Route auth, Vercel Connect setup, and production verification.

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://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

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.

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:

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:

HelperPurpose
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
paramsPath parameters from [name] segments
waitUntil(promise)Extend request lifetime for post-ack background work
requestIpClient 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

HTTP and WebSocket route descriptors. Required on every channel. Seeds durable adapter state for stateful channels. Passed to `send()` via `options.state` on first dispatch. Builds the per-step `channel` argument for event handlers. Can close over `session` to call `setContinuationToken()` later. Lifecycle handlers keyed by stream event type (`message.completed`, `turn.started`, `input.requested`, etc.). `session.failed` receives only `(data, channel)` — no `ctx`. Accepts cross-channel handoffs from `receive()` in another channel's route handler. Fetches bytes for `FilePart.data` URL objects before sandbox staging. Return `null` to pass the URL through to the model provider. Projects a JSON-safe subset of adapter state for instrumentation and dynamic resolvers (`ctx.channel.metadata`, narrowed with `isChannel`).

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:

MethodPathBehavior
POST/eve/v1/sessionCreate session; returns 202 with sessionId and continuationToken
POST/eve/v1/session/:sessionIdContinue session with message and/or inputResponses
GET/eve/v1/session/:sessionId/streamNDJSON 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.

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.

FactoryImport pathDefault routeVerification
eveChanneleve/channels/eve/eve/v1/session, /eve/v1/session/:sessionId, /eve/v1/session/:sessionId/streamrouteAuth (JWT, OIDC, Basic, etc.)
slackChanneleve/channels/slackPOST /eve/v1/slackSlack v0 HMAC (X-Slack-Signature) or custom webhookVerifier
discordChanneleve/channels/discordPOST /eve/v1/discordEd25519 (X-Signature-Ed25519) or webhookVerifier
teamsChanneleve/channels/teamsPOST /eve/v1/teamsBot Framework JWT or webhookVerifier
telegramChanneleve/channels/telegramPOST /eve/v1/telegramX-Telegram-Bot-Api-Secret-Token or webhookVerifier
twilioChanneleve/channels/twilioPOST /eve/v1/twilio/messages, /voice, /voice/transcriptionX-Twilio-Signature over URL + sorted form params
githubChanneleve/channels/githubPOST /eve/v1/githubX-Hub-Signature-256 HMAC or webhookVerifier
`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.

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.

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.

Example: Slack channel

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:

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:

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:

Channel kind. Required when stdin is not a TTY or when `--yes` is passed. Overwrite existing scaffold files. Assume yes for confirmations (e.g. Slackbot creation). Requires an explicit kind.
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).

Emit `{ "channels": ["eve", "slack", ...] }`.
eve channels list
eve channels list --json

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

Choosing a channel

GoalChannel
Browser chat, SDK clients, curlEve channel (default) + client hooks
Slack mentions, DMs, buttonsslackChannel
Discord slash commands, componentsdiscordChannel
Microsoft Teams messages, Adaptive CardsteamsChannel
Telegram bot messagestelegramChannel
SMS or speech-transcribed callstwilioChannel
GitHub @mentions, PR reviewgithubChannel
Internal webhook, WebSocket, custom transportdefineChannel
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.

Troubleshooting

SymptomLikely cause
401 on platform webhookMissing or wrong signing secret; clock skew beyond 5 minutes; custom webhookVerifier returned falsy
401 on /eve/v1/sessionrouteAuth 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 errorFile stem does not match a framework channel name
Twilio signature mismatchwebhookUrl does not match the URL Twilio configured
Session not resumingContinuation token format changed; old-token deliveries dropped after re-key

Related pages

continuationToken contracts, NDJSON stream events, and follow-up routes on the Eve channel. Route auth, webhook verification, secret brokering, and fail-closed defaults. `eveChannel` auth walk, env vars, `eve link`/`eve deploy`, and production verification. Stable `/eve/v1` request and response shapes for session create, continue, and stream. Full `eve channels` flags, exit codes, and the edit-info-dev-build-start loop. Exported `define*` helpers, `AuthFn` strategies, and channel factory types.

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://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

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

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
Authored tool `execute` functions run in the app runtime with full `process.env`. Only sandbox-backed operations cross the trust boundary into `/workspace`.

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:

ToolSandbox executorWorking directory
bashexecuteBashOnSandbox/workspace
read_fileexecuteReadFileOnSandbox/workspace
write_fileexecuteWriteFileOnSandbox/workspace
globexecuteGlobOnSandbox/workspace
grepexecuteGrepOnSandbox/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:

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.

MethodBehavior
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 / writeTextFileUTF-8 (or specified encoding) text I/O; readTextFile supports 1-based line ranges
readBinaryFile / writeBinaryFileRaw byte I/O
readFile / writeFileStream 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.

`agent/sandbox/workspace/skills/` is reserved. Skill discovery seeds `/workspace/skills/` from `agent/skills/`; authoring `workspace/skills/...` is rejected at compile time.

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:

LayoutPathUse when
Shorthandagent/sandbox.tsDefinition only, no seeded files
Folderagent/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.

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" });
  },
});
Backend factory or value. Factory form is invoked lazily on first access and memoized for the process lifetime. Template-scoped hook. Call `use(options?)` to open the template session before snapshot capture. Only template filesystem state carries into later sessions. 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. 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.

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:

ImportBackend nameRuns where
vercel() from eve/sandbox/vercelvercelVercel Sandbox
docker() from eve/sandbox/dockerlocal (Docker)Local Docker daemon via CLI
microsandbox() from eve/sandbox/microsandboxlocal (microsandbox)Lightweight local VM
justbash() from eve/sandbox/just-bashlocal (just-bash)Pure-JS interpreter, no real binaries
defaultBackend() from eve/sandboxResolved at runtimeAvailability chain

Local vs 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. 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.

defaultBackend() accepts a keyed options bag so each inner backend gets typed create options:

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

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

HookScopeWhen it runsWhat persists
bootstrap({ use })TemplateOnce when the template is built (prewarm)Template filesystem and supported backend metadata
onSession({ use, ctx })Durable sessionOnce per session, inside active runtime contextPer-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 kindConditionPrewarm
noneNo bootstrap, no workspace seedsSkipped (templateKey is null)
workspace-contentSeeds onlyKeyed by workspace content hash
bootstrapbootstrap() presentKeyed 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.

`materializeWorkspaceResources` writes `.eve/compile/workspace-resources/` and records content hashes on the manifest. For each graph node with a sandbox, derive `templatePlan` and `templateKey`. Skip nodes where the plan is `none`. Backends capture template state idempotently. Build logs report `reused` vs freshly built templates. `ensureSandboxAccess` calls `backend.create({ templateKey, sessionKey, existingMetadata })`. Missing templates throw `SandboxTemplateNotProvisionedError`; dev mode retries after on-demand prewarm.

When prewarm runs

EnvironmentTriggerFailure behavior
Vercel buildVERCEL and VERCEL_DEPLOYMENT_ID both setBuild fails (runVercelBuildPrewarm)
eve devBackground prewarm on startup and after authored-source changesLogged error; retried on first sandbox access
eve start (production)prewarmBuiltAppSandboxes before servingStartup failure

Prewarm covers template construction only. onSession() still runs at runtime, once per durable session.

Build logs show lines like `Eve: initialized N sandbox templates (X reused, Y built)`. A cache hit reports `reused: true` from the backend.

Network policy and credential brokering

Egress rules accept three forms:

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:

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

SymptomLikely cause
SandboxTemplateNotProvisionedErrorTemplate not prewarmed; dev retries automatically, production needs successful build prewarm
agent/sandbox/workspace/skills/ discovery errorReserved path; move skills to agent/skills/
This tool requires sandbox access on the runtime contextTool called outside a managed harness step
filePath must be an absolute pathModel passed a relative path to read_file/write_file; use /workspace/...
Build fails during sandbox prewarmbootstrap() threw or backend credentials missing; fix bootstrap or Vercel Sandbox access
just-bash / microsandbox missing in productionOptional peers not installed; eve dev auto-installs, production requires explicit install

Related pages

App runtime vs sandbox trust boundaries, secret brokering, and fail-closed defaults. Built-in bash and file tools, compaction, and override patterns. `agent/sandbox/` discovery, path-derived naming, and `.eve/` compile artifacts. Vercel build output, sandbox backend selection, prewarm constraints, and production verification. `ctx.getSandbox()`, `ctx.getSkill()`, and where managed-context APIs are valid. Each subagent gets its own sandbox, independent of its parent.

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://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

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

SurfaceLocationRuntime behavior
Built-in agent toolShipped with every agent (override at agent/tools/agent.ts)Copies the current agent; shares parent sandbox and tools
Local subagentagent/subagents/<id>/agent.ts with defineAgentIsolated child agent; own instructions, tools, skills, sandbox
Remote subagentagent/subagents/<id>/agent.ts with defineRemoteAgentAsync POST to another Eve deployment; parent parks until callback
Scheduleagent/schedules/<name>.{ts,md}Cron-fired session; markdown (task mode) or handler (run)

All subagent kinds lower to the same tool input schema:

{
  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).

Model-visible delegation hint on `defineAgent`. The compiler rejects subagents that omit it.

:::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.

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.

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.

SlotBuilt-in agent toolDeclared subagent
InstructionsInherited (copy)Own instructions.{md,ts}, optional
ToolsInheritedOwn tools/
ConnectionsInheritedOwn connections/
SkillsInheritedOwn skills/
SandboxShared with parentOwn sandbox/, else framework default
HooksInheritedOwn hooks/
StateFreshFresh
ChannelsRoot-onlyRoot-only
SchedulesRoot-onlyRoot-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.

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(),
});
Base URL of the remote Eve deployment. Model-visible delegation description (same role as local subagents). Outbound auth hook from `eve/agents/auth` (`vercelOidc`, `bearer`, `basic`). Route appended to `url` for the create-session request. Defaults to `/eve/v1/session`. Structured return type enforced by the remote in task mode.

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.

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:

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;
}
Standard 5-field cron (`minute hour day-of-month month day-of-week`). Minute granularity. On Vercel, expressions evaluate in UTC.

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.

```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.

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).

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 argPurpose
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
appAuthPre-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 (dispatchScheduleTaskScheduleDispatcher.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. :::

```sh curl -X POST http://localhost:3000/eve/v1/dev/schedules/heartbeat ``` ```json { "scheduleId": "heartbeat", "sessionIds": ["sess_..."] } ```

: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.

`eve dev` does not run cron. Use the dev dispatch route to exercise schedules locally.

When to use what

NeedUse
Same agent, parallel file workBuilt-in agent tool
Different prompt, tools, or sandboxLocal subagent under subagents/<id>/
Specialist owned by another deploymentdefineRemoteAgent
Optional procedure, same identitySkill (load_skill) — lighter than a subagent
Periodic agent workSchedule under agent/schedules/
Schedule that hands off to Slack/Discord/etc.Schedule handler with receive + waitUntil

Related pages

Session/turn/step nesting, durable checkpoints, and parked work (HITL, OAuth, subagents). continuationToken contracts, NDJSON events, and child-session stream attachment. Authored slots, path-derived naming, and subagent inheritance rules. Subagent context isolation and on-demand skills. Deliver schedule output to users via `receive`. Stable session routes and dev-only schedule-dispatch endpoint.

16. Configure agent.ts

defineAgent fields: model (gateway id or LanguageModel), compaction thresholdPercent, modelOptions, experimental.codeMode, outputSchema, and build.externalDependencies packaging.

  • Page Markdown: https://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

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.

agent/agent.ts  →  compileAgentConfig()  →  .eve/manifest (config.*)
                              ↓
                    runtime resolve-agent  →  harness sessions / turns

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.

import { defineAgent } from "eve";

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

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.

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.

```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"),
});
import { defineAgent } from "eve";

export default defineAgent({
  model: "openai/gpt-5.5",
  modelOptions: {
    providerOptions: {
      openai: {
        reasoningEffort: "high",
        reasoningSummary: "auto",
      },
    },
  },
});
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. 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. 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.

Routing at compile time

Eve classifies how the model reaches inference and embeds routing in the compiled manifest:

Authored valueRoutingRuntime behavior
Gateway id string (anthropic/claude-sonnet-4.6)gatewayRouted through the Vercel AI Gateway
gateway(...) instancegatewaySame as a string id
Direct provider instance (anthropic(...))externalBypasses the gateway; talks to the provider endpoint
Gateway id + providerOptions.gateway.byokgateway with byokForwards a bring-your-own-key provider block through the gateway
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.`

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.

export default defineAgent({
  model: "anthropic/claude-opus-4.8",
  compaction: {
    thresholdPercent: 0.75,
  },
});
Fraction of the primary model context window that triggers compaction. Defaults to `0.9` at runtime when omitted. Lower values compact sooner. Optional model used only for compaction summaries. When omitted, Eve uses the active turn model for the summary call. Same escape hatch as `modelContextWindowTokens`, but for the compaction summary model.

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.

export default defineAgent({
  model: "openai/gpt-5.4",
  experimental: { codeMode: true },
});
`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.
ResolutionEffective value
experimental.codeMode: trueCode mode on
experimental.codeMode: falseCode mode off (overrides env)
Flag omittedFalls 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.

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.

export default defineAgent({
  model: "openai/gpt-5.4-mini",
  build: {
    externalDependencies: ["sharp", "fixture-trace-only-dep"],
  },
});
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).

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

FieldTypeDefaultScope
modelstring | LanguageModelanthropic/claude-sonnet-4.6 when agent.ts omittedRuntime turns
modelContextWindowTokensnumbercatalog lookupCompaction threshold math
modelOptions{ providerOptions? }noneModel call options
compactionobjectharness defaults (thresholdPercent0.9)Context management
experimental.codeModebooleanenv backstop, else falseTool exposure mode
outputSchemaStandard Schema or JSON SchemanoneTask-mode structured output
build.externalDependenciesstring[]noneHosted build packaging
descriptionstringnoneSubagent 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

ConcernLocation
System promptagent/instructions.md or agent/instructions.ts
Per-tool HITL approvalagent/tools/*.ts
Inbound auth and network policychannel definitions (agent/channels/)
Sandbox and workspaceagent/sandbox/
Telemetryagent/instrumentation.ts
Eval suitesevals/

Troubleshooting

SymptomLikely cause
The "model" field is required.agent.ts exists but omits model
does not have known AI Gateway context window metadataUnlisted gateway model id without modelContextWindowTokens override
to provide a valid AI SDK language modelProvider instance missing provider, modelId, doGenerate, or doStream; or unsupported specificationVersion
missing a "description" fieldLocal subagent agent.ts without description
Expected ... to match the public Eve shapeUnknown 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

Where `agent.ts` lives in the authored tree and what compiles into `.eve/` artifacts. How compaction, built-in tools, and the agent loop consume `agent.ts` config. Per-subagent `defineAgent` overrides, required `description`, and task-mode `outputSchema`. Model credentials, `eve build` / `eve deploy`, and hosted `externalDependencies` tracing. Exported `defineAgent` types, `AgentDefinition`, and related `define*` helpers.

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://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

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).

RouteAuth
POST /eve/v1/sessionrouteAuth walk
POST /eve/v1/session/:sessionIdrouteAuth walk
GET /eve/v1/session/:sessionId/streamrouteAuth walk
GET /eve/v1/healthAlways 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:

OutcomeBehavior
Returns SessionAuthContextAccept request; stop walk
Returns null / undefinedSkip to next entry
Throws UnauthenticatedError / ForbiddenErrorReject with structured 401 / 403
Every entry skips (including auth: [])Reject with 401
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:

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

HelperUse 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
`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.

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".
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:

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:

FieldMeaning
auth.currentCaller on the active inbound turn
auth.initiatorCaller 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.

Route auth decides access at the HTTP boundary. There is no second per-session ownership ACL stacked on top.

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

Vercel OIDC token pulled by `eve link` / `vercel env pull`. Gateway model ids authenticate through Vercel OIDC with no provider keys to manage. Direct AI Gateway API key alternative to OIDC. Example direct provider key when not using the gateway.

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

VariableRole
VERCELSet during hosted builds and Vercel runtime; triggers .vercel/output emission and Vercel sandbox backend selection
VERCEL_ENVproduction, preview, or development; used by placeholderAuth() and OIDC user-token environment matching
VERCEL_TARGET_ENVDeployment target environment for OIDC user-token matching
VERCEL_PROJECT_IDCurrent project binding for vercelOidc() and stable sandbox template scoping
VERCEL_DEPLOYMENT_IDEnables build-time sandbox prewarm; keys source-graph templates
VERCEL_AUTOMATION_BYPASS_SECRETLocal bypass for Vercel preview protection when driving remote deployments with eve dev <url>
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.

eve link and eve deploy

eve link

Interactive only — team and project pickers are the point of the command.

Run from an Eve project root (`eve init` scaffold present). ```bash eve link ``` Select team and project. Eve runs `vercel link`, then `vercel env pull` so a model credential lands in `.env.local`. Confirm `VERCEL_OIDC_TOKEN` or `AI_GATEWAY_API_KEY` appears in an env file. A running `eve dev` reloads env files automatically.

Re-linking shows the current link and offers “Link to another project”; the new choice is authoritative.

In CI (non-TTY):

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 stateTTYBehavior
Already linkedAnyDeploy immediately
UnlinkedInteractiveWalk eve link pickers, then deploy
UnlinkedNon-interactiveExit with guidance to run eve link first
eve deploy

On success the command prints the production URL when available.

Build output

eve build

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

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

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:

PriorityBackendCondition
1vercel()process.env.VERCEL is set
2docker()Docker daemon reachable
3microsandbox()Platform supported (Apple Silicon macOS or glibc Linux with KVM)
4justbash()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.

If a Vercel sandbox template is not provisioned at runtime, Eve throws `SandboxTemplateNotProvisionedError` with guidance to run `eve build` or `prewarmAppSandboxes()` first.

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 kindKeyed by
workspace-content (seed files only)Skills and workspace file contents (contentHash)
bootstrapOptional revalidationKey(), authored sandbox source hash, and seed contents
source-graphFull 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).

Prewarming covers template construction only. `onSession()` still runs at runtime, once per session. If build-time prewarm fails, the build fails. The same bootstrap would break at runtime.

Production verification

```bash eve build ``` Confirm `.eve/discovery/diagnostics.json` has no blocking errors. On Vercel, confirm prewarm lines in the build log. ```bash eve deploy ``` Or push to a Git-connected Vercel project. ```bash curl https:///eve/v1/health ``` ```json {"ok":true,"status":"ready","workflowId":"..."} ``` ```bash curl -X POST https:///eve/v1/session \ -H 'content-type: application/json' \ -H 'authorization: Bearer ' \ -d '{"message":"Hello from production"}' ``` ```json {"ok":true,"sessionId":"","continuationToken":"eve:..."} ``` ```bash curl https:///eve/v1/session//stream \ -H 'authorization: Bearer ' ```

For interactive smoke tests against a remote deployment:

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

Trust boundaries, secret brokering, and the pre-production security checklist. `defineChannel`, platform factories, and webhook verification beyond the default Eve HTTP channel. Workspace seeding, `bootstrap`/`onSession` lifecycle, and proxy execution for shell/file tools. Full `eve link`, `eve deploy`, and `eve build` flag and artifact reference. Request/response shapes, NDJSON stream events, and stable route inventory. Discovery diagnostics, 401 failure modes, and build/prewarm errors.

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://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

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.

import { defineState } from "eve/context";

export const budget = defineState("my-agent.budget", () => ({ count: 0, cap: 25 }));
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. Function that produces the starting value on first access within a context.

defineState returns a StateHandle<T>:

Reads the current value. Returns `initial()` on first access within the active context. Replaces the value with `fn(current)`.

Both methods require an active Eve context. Use them from tools, hooks, channel handlers, and other framework-invoked callbacks:

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.

Values must be JSON-safe. Author-defined keys have no custom codec; the serializer stores them as-is.

State does not reset between turns by default. To clear per turn, overwrite from a lifecycle hook:

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

ConcerndefineStateExternal store / connection
ScopeOne sessionCross-session, cross-user
LifetimeDies with the sessionIndependent of conversation
Use caseRunning counters, in-conversation plans, glossariesDurable 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.

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/:

FileSlug
agent/hooks/audit.tsaudit
agent/hooks/auth/load-profile.tsauth/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 typeTypical use
session.startedSession bootstrap, audit
turn.started / turn.completedPer-turn resets, metrics
message.completedModel output logging
action.resultTool result inspection
step.started / step.completed / step.failedStep-level tracing
turn.failed / session.failed / session.completedFailure and completion handling
session.waitingParked-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:

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:

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:

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:

FieldTypeDescription
idstringActive session identifier
turn.idstringCurrent turn identifier
turn.sequencenumberTurn sequence within the session
auth.currentSessionAuthContext | nullCaller for the active inbound turn
auth.initiatorSessionAuthContext | nullCaller that started the durable session
parentSessionParent | undefinedPresent 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.
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.
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.

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.

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());
  },
});
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`). 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. Throws `ConnectionAuthorizationRequiredError` to gate execution on authorization without resolving a token first. The runtime converts the error into a consent prompt for interactive strategies. Calling `ctx.getToken()` or `ctx.requireAuth()` on a tool without an `auth` field throws immediately.

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).

SurfacedefineState / ctxNotes
defineTool(...).execute(input, ctx)ValidToolContext when auth is declared
defineHook event handlersValidHookContext
Channel adapter event handlersValidSessionContext
defineDynamic resolver callbacksValidFramework builds SessionContext internally
Sandbox onSession hookValidReceives { ctx, use }; runs inside ALS
Async boundaries within the same authored callback chainValidALS propagates across await
Module top-level evaluationThrowsNo active context at import time
Build scripts / discovery-time codeThrowsOutside runtime execution
Schedule run handlerNo ctxReceives ScheduleHandlerArgs (receive, waitUntil, appAuth)
Sandbox bootstrap hookNo ctxReceives { use } only; runs at build/prewarm
Instrumentation setupNo ctxDomain-specific arguments
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.

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

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

NeedUse
Observe runtime events (audit, metrics, alerting)defineHook events.<type>
Provide structured input to the model on demanddefineTool
Change model context based on stream eventsdefineDynamic under agent/instructions/
Subscribe to platform-specific ingressChannel adapter handler
Durable per-session working memorydefineState

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

Session/turn/step nesting, workflow checkpoints, and crash-resume semantics that make defineState durable. NDJSON stream event vocabulary that defineHook subscribers observe. defineTool execute(ctx), auth strategies, and requireAuth flows. defineDynamic resolvers that run after hooks on the same stream events. Subagent state and hook isolation; schedule run handlers without ctx. Full define* import map and exported context types.

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://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

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:

SurfaceConfigured in instrumentation.ts?What it records
Workflow run tags ($eve.*)No — automaticFramework-owned attributes on each Vercel Workflow run. Dashboards stitch session, turn, and subagent runs into a tree and surface model and token usage.
OpenTelemetry exportYes — setup, recordInputs, recordOutputs, functionIdWhere AI SDK spans are exported and what they record.
Runtime context eventsYes — 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.

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.

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

Fields

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). Records full message history on each step span. Set `false` for sensitive inputs or to reduce span payload size. Records model outputs on spans. Set `false` to disable output recording. Overrides `ai.telemetry.functionId` on spans. Defaults to the agent name. 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.

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:

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.

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.

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

TagValues / meaning
$eve.type"session", "turn", or "subagent"
$eve.parentSession id of the immediate parent
$eve.rootSession id of the root session (group a tree with $eve.root=<id>)
$eve.parent_callParent runtime-action tool call id (subagent rows only)
$eve.parent_turnParent turn id that dispatched the subagent (subagent rows only)
$eve.subagentCompiled graph node id (subagent runs only)
$eve.triggerChannel kind that started the run
$eve.titleTruncated title from the first user message

Per-turn usage tags

Written on each step of a turn, accumulating cumulative totals (last write wins):

TagMeaning
$eve.modelModel id for the turn
$eve.input_tokensRunning input token count
$eve.output_tokensRunning output token count
$eve.cache_read_tokensRunning cache-read token count
$eve.cache_write_tokensRunning cache-write token count
$eve.tool_countNumber 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:

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:

FieldPurpose
descriptionHuman-readable summary (shown in --list output)
judgeOverride judge model for t.judge.* on this eval
tagsFilter with eve eval --tag
metadataPassed through to reporters
timeoutMsPer-eval timeout (overridden by CLI --timeout)
reportersReporters 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

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:

FieldDefaultPrecedence
judge.modelnonePer-call model → per-eval judge → config judge
reportersnoneSuppressed by --skip-report
maxConcurrency8CLI --max-concurrency overrides
timeoutMsnonePer-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:

MethodPurpose
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.replyLast assistant message on the primary session
t.events / t.sessionId / t.stateStream 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

SurfaceExamplesDefault severity
Run-level methodst.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/expectdepends on builder
t.judge.autoevals.*t.judge.autoevals.closedQA("cites a source")soft

Run-level methods observe the whole run:

MethodAsserts
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:

BuilderScoresDefault
includes(substring)substring matchgate
equals(value)deep structural equalitygate
matches(schema)Standard Schema validationgate
similarity(expected)normalized Levenshtein, 0–1soft

Gate vs soft

SeverityBehavior
GateFailed gate → eval failedeve 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).

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
`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.

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.

GraderGrades
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

Create `evals/evals.config.ts` and one or more `evals/**/*.eval.ts` files with `defineEval`. ```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 ``` ```bash eve eval --url https:// # deployed instance eve eval --strict --junit .eve/junit.xml # CI: soft thresholds fail too ```

CLI flags

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`. Remote agent URL. Skips local dev server startup. Run only evals carrying any requested tag. Fail exit code when any soft assertion falls below its threshold. Print discovered evals without running them. Use `--json` for machine-readable output. Per-eval timeout in milliseconds. Non-negative integer. Max concurrent eval executions. Positive integer; default `8`. Output run summary as JSON. Suppresses the console reporter. Write JUnit XML to the given path. Skip config-level and eval-defined reporters (e.g. Braintrust). Stream per-eval `t.log` lines to stdout.

Exit codes and verdicts

Exit codeMeaning
0Every eval passed its gates (and soft thresholds under --strict)
1Any eval failed — gate miss, execution error, or strict threshold miss
2Configuration error — no evals discovered, no tag matches, invalid flags, missing evals.config.ts

Per-eval verdicts:

VerdictMeaning
passedNo execution error; every gate held; every soft threshold met
failedGate assertion failed or execution errored (timeout, transport, thrown task)
scoredEvery gate held but a soft assertion fell below its threshold

Artifacts

Each run writes artifacts under .eve/evals/<timestamp>/:

FileContents
summary.jsonAggregated run outcome
results.jsonlOne line per eval result
evals/<id>.jsonPer-eval assertions, verdict, logs
evals/<id>.events.ndjsonCaptured 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

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

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:

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

SymptomLikely cause
No spans in OTel backendMissing agent/instrumentation.ts or setup not calling registerOTel
eve eval exits 2 with config errorMissing evals/evals.config.ts — create defineEvalConfig({}) at minimum
Judge assertions always failNo judge.model resolved at any level, or missing gateway credentials
Eval passes locally, fails in CIRun eve eval --strict; check model-provider credentials in the CI environment
Duplicate eval id errorArray-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

Where `evals/` and `agent/instrumentation.ts` fit in the authored tree and how path-derived naming works. Full `eve eval` command reference, exit codes, and the edit-info-dev-build-start loop. The HTTP session protocol evals drive and the NDJSON events assertions read. Session/turn/step nesting, workflow durability, and the run tree behind `$eve.*` tags. Model credentials for evals and the Vercel Agent Runs observability surface. `defineInstrumentation`, `defineEval`, `defineEvalConfig`, and eval entrypoint import paths.

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://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

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

┌─────────────────────────────────────────────────────────────┐
│  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)      │
└───────────────────────────────────────────────────────────────┘
LayerImportResponsibility
TypeScript clienteve/clientScripts, tests, evals, custom UIs without framework state
Framework hookseve/react, eve/vue, eve/svelteSession lifecycle, optimistic projection, reactive data
Route mountingeve/next, eve/nuxt, eve/sveltekitDev proxy / production rewrite to Eve service on same origin
Framework hooks and the raw client share `ClientAuth`, `HeadersValue`, `SendTurnPayload`, `SessionState`, and stream event types exported from `eve/client`.

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.

import { Client } from "eve/client";

const client = new Client({
  host: "http://127.0.0.1:3000",
});
Origin where Eve routes are mounted. Use `""` for same-origin browser calls against `/eve/v1/*`. Bearer or Basic credentials. Values can be static strings or async functions resolved before every HTTP call, including stream reconnects. Custom headers on every request. Static map or per-request resolver (for example bypass tokens). Maximum stream reconnection attempts per turn after transient disconnects. When `true`, keeps `continuationToken` and `sessionId` after `session.completed` so the next `send()` continues the same durable conversation instead of starting fresh.

Utility methods:

MethodRoutePurpose
health()GET /eve/v1/healthFail fast before creating a session
info()GET /eve/v1/infoAgent inspection payload
fetch(path, init)Any path on hostAuthenticated 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.

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.

User turn text or multi-part content (text, file attachments as data URLs). HITL responses for pending approvals or `ask_question` prompts. Ephemeral page context for the next model call only. Never persisted to durable session history. Structured output schema; the client lowers Standard Schema implementations to JSON Schema before send. Cancels the POST and stream. Arm timeouts before `await send()` so they cover both phases. Per-turn headers merged on top of client-level headers.

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

```ts Bearer const client = new Client({ host: "https://agent.example.com", auth: { bearer: async () => await getAccessToken(), }, }); ```
const client = new Client({
  host: "https://agent.example.com",
  auth: {
    basic: {
      username: "agent-client",
      password: async () => await getRotatingSecret(),
    },
  },
});
const client = new Client({
  host: "https://agent.example.com",
  headers: async () => ({
    "x-vercel-protection-bypass": await getBypassToken(),
  }),
});

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.

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`.

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

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

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

EventUI use
message.receivedConfirm user message landed
reasoning.appendedRender reasoning deltas
message.appendedRender assistant text deltas
actions.requestedShow tool calls
action.resultShow tool results
input.requestedPause for HITL approval or question
result.completedStructured output when outputSchema was set
session.waitingEnable composer for next turn
session.completedTerminal conversation
session.failedTerminal failure

Import helpers from eve/client:

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

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 eventWhen emitted
client.message.submittedOptimistic user message before message.received
client.message.failedSubmitted message failed before confirmation
client.input.respondedHITL 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:

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.

FrameworkImportReturn shape
Reacteve/reactSnapshot object + send, stop, reset via useSyncExternalStore
Vueeve/vueReactive ComputedRefs + commands; auto-imported by eve/nuxt
Svelteeve/svelteRune-friendly getters on a reactive binding

Returned state

FieldTypePurpose
dataTDataReducer projection (default: { messages })
status"ready" | "submitted" | "streaming" | "error"Composer gating
errorError | undefinedLast failure
eventsHandleMessageStreamEvent[]Authoritative server stream
sessionSessionStateSerializable cursor
send(SendTurnPayload) => Promise<void>Dispatch turn
stop() => voidAbort in-flight turn
reset() => voidClear local state, new session

Basic React chat

"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

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:

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:

const agent = useEveAgent({
  prepareSend: (input) => ({
    ...input,
    clientContext: { route: location.pathname },
  }),
});

Lifecycle callbacks

Fires for each authoritative stream event. Fires on turn failure. Fires when a turn settles with final snapshot. Fires when `SessionState` advances; use to persist cursor. Runs immediately before each `send`; return modified `SendTurnPayload`. Project submitted user messages before `message.received`.

Resumable sessions

Persist the full session object (sessionId, continuationToken, streamIndex):

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():

import type { NextConfig } from "next";
import { withEve } from "eve/next";

const nextConfig: NextConfig = {};
export default withEve(nextConfig);
OptionDefaultPurpose
eveRootNext.js app rootPath to Eve agent directory
eveBuildCommand"eve build"Eve Vercel service build command
configureVercelOutputtrueWrite experimentalServices to .vercel/output/config.json
servicePrefix"/_eve_internal/eve"Private Vercel route namespace
devServerTimeoutMs180000Dev 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

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:

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.

Install `eve`, run `eve init` if needed, and confirm `agent/` exists at the app root or set `eveRoot`. Add `withEve`, `eve/nuxt`, or `eveSvelteKit` so `/eve/v1/*` is same-origin. Use `useEveAgent` for browser UIs or `Client` + `ClientSession` for scripts and custom integrations. Match client `auth`/`headers` to the Eve channel policy in `agent/channels/eve.ts`. Store `agent.session` via `onSessionChange` for reload survival.

When to use which surface

Use caseSurface
Browser chat UI with streaming stateuseEveAgent + framework plugin
Backend job, eval, test harnessClient + ClientSession
Custom UI projection shapeuseEveAgent({ reducer }) or manual for await over MessageResponse
Attach to in-progress stream after reloadclient.session(persistedState).stream()
Agent discovery from a scriptclient.info()
`EveMessage[]` from the default reducer follow AI SDK `UIMessage` conventions and drop into AI SDK UI primitives that accept `UIMessage[]`.

Related pages

continuationToken vs sessionId, POST routes, NDJSON vocabulary, and reconnect semantics. Stable `/eve/v1` routes, request/response shapes, and event vocabulary. Route auth on the Eve channel, env vars, and production verification. Fail-closed route auth defaults and trust boundaries. `define*` helpers, client entrypoints, and exported types. Shortest path from `eve init` to a running HTTP session.

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://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

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

CommandDescription
eve init [target]Scaffold a new agent, or add one to an existing project directory
eve infoPrint resolved application paths, discovered surface, messaging routes, and artifact locations
eve buildCompile .eve/ artifacts and build host output; prints the output directory
eve startServe the built .output/ app; prints the listening URL
eve devStart 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 linkLink the directory to a Vercel project and pull AI Gateway credentials
eve deployDeploy the agent to Vercel production (links first when needed)
eve evalRun evals against the local app or a remote target
eve channels add [kind]Scaffold a channel interactively, or by kind (slack | web)
eve channels listList user-authored channels

Global flags apply to the root program:

FlagDescription
-V, --versionPrint the installed eve package version
-h, --helpPrint 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

CodeWhen
0Command completed successfully; help requested (--help); eve eval when every eval passed
1Runtime 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
2eve 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.

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.

.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/ │ └── / # 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/:

PathWhen
.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

Change instructions, skills, tools, connections, channels, or `agent/agent.ts` under `agent/`. Run `eve info` to confirm files were discovered and read diagnostics before starting a server. Run `eve dev` for watch-mode rebuilds, sandbox prewarm, and the terminal UI. Run `eve build` to compile `.eve/` artifacts and produce host output. Run `eve start` against the built `.output/` directory before deploying.

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

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

TargetBehavior
my-agent (name)Scaffold a fresh project in my-agent/
. or existing directoryAdd an agent to that project (package.json required; agent/ must not exist)
OmittedSame 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.

Add the Web Chat application (Next.js). Rejected when adding to an existing project; run `eve channels add web` afterward.

eve info

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.

Emit the stable machine-readable contract: `appRoot`, `agentRoot`, `layout`, `status`, `diagnostics`, `model`, `instructions`, `skills`, `tools`, `channels`, `messaging`, and `artifacts`.

eve build

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

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>.

Host interface to bind. Defaults to `0.0.0.0`. Port to listen on. Defaults to `$PORT`, then `3000`.

eve dev

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.

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).

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.

Host interface to bind. Cannot be used with `--url`. Port to listen on. Cannot be used with `--url`. Defaults to `$PORT`, then `2000`. When omitted, Eve retries on port conflicts. Connect the terminal UI to an existing server URL instead of starting one. Cannot combine with `--host`, `--port`, or `--no-ui`. Start the server without the interactive UI. Headless mode prints `server listening at `. Title shown in the terminal UI. Defaults to the app folder name (or remote host for `--url`). Pre-fill the prompt input after launching the UI. Requires the interactive UI. Tool-call rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `auto-collapsed`. Reasoning rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `full`. Subagent-section rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `auto-collapsed`. Connection-authorization rendering: `full` | `collapsed` | `auto-collapsed` | `hidden`. Default: `full`. Assistant header statistic: `tokens` | `tokensPerSecond`. Default: `tokensPerSecond`. Model context window size, shown as a usage percentage in the UI. Server/agent logs to show: `all` | `stderr` | `sandbox` | `none`. Default: `stderr`. For manual cleanup after stopping `eve dev`, delete `.eve/dev-runtime/snapshots/` or `.eve/sandbox-cache/local/templates/`.

eve link

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

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

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.

Remote agent URL. Skips local host startup. Run only evals carrying any of the given tags. Below-threshold scores also fail the exit code (exit `1`). Print discovered evals without running them. Per-eval timeout in milliseconds (non-negative integer). Max concurrent eval executions (positive integer). Default: `8` when neither flag nor `evals.config.ts` sets it. Output run summary as JSON. Write JUnit XML results to the given file path. Skip eval-defined reporters (for example Braintrust). Stream per-eval `t.log` lines to stdout.

Run artifacts are written under .eve/evals/<timestamp>/ (summary.json, results.jsonl, per-eval detail files).

eve channels add

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.

Overwrite existing channel files. Assume yes for confirmations. Requires an explicit `kind`.

eve channels list

eve channels list [--json]

Lists user-authored channel names from the current project.

Output `{ channels: string[] }` as JSON.

Related pages

Scaffold with `eve init`, verify with `eve info`, iterate with `eve dev`, and create HTTP sessions. Authored slots under `agent/` and what compiles into `.eve/` artifacts. `eve link`/`eve deploy` flows, Vercel output, and production verification. Authoring evals, judges, and `eve eval` reporters. `defineChannel` authoring and platform channel factories. Discovery diagnostics, dev server PID conflicts, and common failure modes.

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://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

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 pathRuntime identity
agent/tools/get_weather.tstool get_weather
agent/connections/linear.tsconnection linear
agent/channels/slack.tschannel slack
agent/skills/deploy.mdskill deploy
evals/smoke/basic.eval.tseval 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.

import { defineAgent } from "eve";

export default defineAgent({ model: "anthropic/claude-opus-4.8" });
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

HelperImport fromAuthored at
defineAgenteveagent/agent.ts
defineRemoteAgenteveagent/subagents/<id>/agent.ts
defineTooleve/toolsagent/tools/<name>.ts
defineDynamiceve/tools, eve/skills, eve/instructionsmatching agent/ slot
defineMcpClientConnection, defineOpenAPIConnectioneve/connectionsagent/connections/<name>.ts
defineChanneleve/channelsagent/channels/<name>.ts
eveChannel, slackChannel, and other platform factorieseve/channels/<platform>agent/channels/<platform>.ts
defineSkilleve/skillsagent/skills/<name>.ts or .md
defineInstructionseve/instructionsagent/instructions.ts or agent/instructions/
defineHookeve/hooksagent/hooks/<slug>.ts
defineScheduleeve/schedulesagent/schedules/<name>.ts or .md
defineStateeve/contextmodule scope in tools, hooks, or lifecycle code
defineSandboxeve/sandboxagent/sandbox.ts
defineInstrumentationeve/instrumentationagent/instrumentation.ts
defineEvaleve/evalsevals/<path>.eval.ts
defineEvalConfigeve/evalsevals/evals.config.ts
useEveAgenteve/react, eve/vue, eve/sveltefrontend components

defineAgent

defineAgent accepts an additive configuration object. Key fields:

Primary model: an AI Gateway model ID or any AI SDK-compatible language model. Human-readable purpose. Required for subagents; surfaced as the lowered subagent tool description. Context compaction settings, including `thresholdPercent`. Provider-specific model call options. Escape hatch when the gateway catalog cannot resolve context window metadata. Structured return type for task-mode runs (subagents, schedules, remote jobs). Interactive turns ignore it unless the client supplies a per-message schema. Opt-in unstable capabilities (for example `codeMode`). Packaging options such as `externalDependencies`.

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:

Model-facing tool description. Validated tool input. Optional structured output schema. Handler receiving validated input and {@link ToolContext}. Per-call HITL gate. Use `always`, `once`, or `never` from `eve/tools/approval`. Authorization strategy. When set, `ctx.getToken()` and `ctx.requireAuth()` become available. Projection controlling what the model sees as the tool result. Channel handlers always receive the full output.

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:

SlotReturn shapeEvents honored
agent/tools/defineTool(...), Record<string, defineTool(...)>, or nullsession.started, turn.started, step.started
agent/skills/defineSkill(...), record map, or nullsession.started, turn.started
agent/instructions/defineInstructions({ markdown }) or nullsession.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

MemberTypeUse
session.idstringCurrent session identifier
session.authSessionAuthCaller and initiator principals
session.turnSessionTurnActive turn metadata
session.parentSessionParent?Parent lineage for subagent sessions
getSandbox()Promise<SandboxSession>Live sandbox handle; throws when unavailable
getSkill(identifier)SkillHandleNamed skill visible to the current agent

ToolContext additions

MemberUse
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

MemberUse
agent.nameCurrent agent name
agent.nodeIdOptional node identifier
channel.kindChannel kind discriminator
channel.continuationTokenActive 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:

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) {
    /* ... */
  },
});
HelperBehavior
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:

ExportFramework toolNotes
bashShell execution
readFileFile readerRead-before-write stamps reset on compaction
writeFileFile writerEnforces read-before-write and stale-read detection
globPath glob search
grepContent regex search
webFetchHTTP fetch
webSearchProvider-managed searchLocal execute is a throwing stub; override with defineTool in agent/tools/web_search.ts
todoDurable todo listSpreading preserves framework closure-bound state
loadSkillSkill loaderOnly 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:

ToolRegistration
ask_questionFramework tool in the default harness
agentSubagent delegation (lowered from agent/subagents/)
connection_searchFramework 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:

HelperUse
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 pathExports
eve/evalsdefineEval, defineEvalConfig, eval types, EveEvalTurnFailedError
eve/evals/expectincludes, equals, matches, similarity assertion builders
eve/evals/reportersBraintrust, JUnit, EvalReporter
eve/evals/loadersloadJson, 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)

ExportRole
ClientHTTP client bound to one host and auth config
ClientSessionOne conversation; tracks continuation token and stream cursor
ClientErrorNon-success HTTP responses
defaultMessageReducerDefault NDJSON stream-to-message reducer
EveAgentStoreStateful client store for UI integrations
createDataUrlFilePart, createTextWithFileContentFile part helpers
MessageResponseTurn response wrapper
resolveTextToResponse, resolveTextToResponsesText-to-input-response helpers

ClientOptions fields:

Base URL of the Eve agent server. `{ bearer: TokenValue }` or `{ basic: { username, password } }`. Resolved before every request. Static or per-request custom headers. Stream reconnection attempts per message turn. Keep continuation token after `session.completed` for follow-up prompts.

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

ImportExports
eve/react, eve/vue, eve/svelteuseEveAgent, reducer types, message types
eve/nextwithEve() Next.js config wrapper
eve/nuxtNuxt module default export
eve/sveltekiteveSvelteKit() Vite plugin

useEveAgent wraps Client/ClientSession with framework-native state, streaming reducers, and HITL input handling.

Imports at a glance

ImportHolds
evedefineAgent, defineRemoteAgent, agent types
eve/toolsdefineTool, defineDynamic, disableTool, ExperimentalWorkflow, tool types, toolResultFrom, tool factories
eve/tools/defaultsBuilt-in tools as plain values
eve/tools/approvalalways, once, never, NeedsApprovalContext
eve/connectionsConnection define helpers, auth types, authorization errors
eve/channelsdefineChannel, route verbs, isChannel, channel metadata types
eve/channels/eveeveChannel
eve/channels/authRoute auth strategy helpers and verifiers
eve/channels/{slack,discord,teams,telegram,twilio,github,linear}Platform channel factories
eve/hooksdefineHook, HookContext
eve/schedulesdefineSchedule
eve/skillsdefineSkill, defineDynamic, SkillHandle
eve/instructionsdefineInstructions, defineDynamic
eve/contextdefineState, StateHandle, session types
eve/sandboxdefineSandbox, backends, sandbox types
eve/instrumentationdefineInstrumentation, isChannel
eve/evalsEval define helpers and types
eve/clientClient, ClientSession, reducers, stream types
eve/agents/authOutboundAuthFn 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

`defineAgent` fields, compaction, model options, and build packaging. `defineTool` execution, auth, approval, and dynamic resolution. `defineState`, `defineHook`, and where `ctx` APIs are valid. Built-in tools, compaction defaults, and override patterns. `defineEval`, judges, assertions, and reporters. `Client`, `ClientSession`, `useEveAgent`, and framework plugins.

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://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

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.

MethodPathAuthAvailability
GET/eve/v1/healthNoneAlways
GET/eve/v1/infolocalDev() then vercelOidc()Always
POST/eve/v1/sessioneveChannel auth chainAlways
POST/eve/v1/session/:sessionIdeveChannel auth chainAlways
GET/eve/v1/session/:sessionId/streameveChannel auth chainAlways
GET or POST/eve/v1/connections/:name/callback/:tokenNone (token is the capability)Always
POST/eve/v1/callback/:tokenNone (token is the capability)Always
GET/eve/v1/dev/runtime-artifactsNoneDev only
POST/eve/v1/dev/runtime-artifacts/rebuildNoneDev only
POST/eve/v1/dev/schedules/:scheduleIdNoneDev 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 publicGET /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 authGET /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 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.


:::endpoint GET /eve/v1/health Liveness probe for load balancers and uptime monitors. Always public. :::

curl http://127.0.0.1:3000/eve/v1/health
{
  "ok": true,
  "status": "ready",
  "workflowId": "<workflow-entry-id>"
}
Always `true` when the handler responds. Runtime readiness label. Currently `"ready"`. The workflow entry reference id for the running agent.

:::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).

curl http://127.0.0.1:3000/eve/v1/info

The top-level payload uses kind: "eve-agent-info" and version: 1. Key sections:

Name, description, model (`id`, `contextWindowTokens`, `providerOptions`, `routing`, `endpoint`), `outputSchema`, and source paths (`agentRoot`, `appRoot`). `authored`, `framework`, `available`, `disabledFramework`, `dynamic` resolvers, and `reserved` tool names. `true` in development mode, `false` in production. Indicates whether dev-only `/eve/v1/dev/*` routes are mounted. `discoveryErrors` and `discoveryWarnings` counts from agent discovery.

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.

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. One-turn client/page context. Strings and arrays are prefixed `Client context:\n` and injected as user context. Objects are JSON-stringified first. Optional JSON Schema object for a structured turn result. When set, the finalized payload appears on `result.completed` in the stream. Session mode. Defaults to `"conversation"` (enables `requestInput` for HITL). `"task"` runs without interactive input requests. 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).
curl -X POST http://127.0.0.1:3000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"Summarize the latest forecast."}'
{
  "ok": true,
  "sessionId": "sess_…",
  "continuationToken": "…"
}
StatusMeaning
202Session created and turn enqueued
400Invalid JSON, missing message, bad attachment, or upload policy violation (413 too large, 415 bad media type)
401 / 403Auth walk failed
204onMessage returned null — message accepted but not dispatched
500onMessage handler threw
Opaque resume token for follow-up requests. Store it client-side. Durable session id. Also returned in the `x-eve-session-id` header.

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. :::

The durable session id from create or prior continue responses. Current resume token. Stale or missing tokens are rejected. Follow-up user message. Optional when `inputResponses` is provided. Non-empty array of HITL responses. Each entry requires `requestId`; include `optionId` and/or `text` as appropriate. Same semantics as session create. Per-turn structured output schema override.

At least one of message or inputResponses must be non-empty.

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."}'
{
  "ok": true,
  "sessionId": "<sessionId>"
}

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. :::

Session to stream. Non-negative event index for reconnect or rewind. Omit to stream from the latest live position.
curl http://127.0.0.1:3000/eve/v1/session/<sessionId>/stream
{"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":"…"}}

Stream headers

HeaderValue
content-typeapplication/x-ndjson; charset=utf-8
x-eve-stream-formatndjson
x-eve-stream-version15
x-eve-session-idSession id
cache-controlno-store, no-transform
x-accel-bufferingno

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

EventMeaning
session.startedDurable session created. data.runtime may include agent id, model id, Eve version, and build metadata. Child subagent sessions include data.invocation.
turn.startedNew turn began (data.turnId, data.sequence).
message.receivedInbound user message accepted (data.message is a text summary for multimodal input).
turn.completedTurn finished successfully.
turn.failedTurn failed (data.code, data.message, optional data.details).
session.waitingParked with data.wait: "next-user-message".
session.completedTerminal session success.
session.failedTerminal session failure (data.code, data.message, data.sessionId).

Model steps

EventMeaning
step.startedModel call began.
step.completedModel call finished (data.finishReason, optional data.usage token counts).
step.failedModel 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

EventMeaning
message.appendedText delta (data.messageDelta, cumulative data.messageSoFar).
message.completedFinalized text block (data.message, data.finishReason). May fire multiple times per turn.
reasoning.appendedReasoning delta (data.reasoningDelta, data.reasoningSoFar).
reasoning.completedFinalized reasoning block.
result.completedStructured output when outputSchema was requested (data.result).

Tools and human input

EventMeaning
actions.requestedModel requested tool calls (data.actions).
action.resultTool result (data.result, data.status: "completed" | "failed", optional data.error).
input.requestedRun 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

EventMeaning
subagent.calledChild workflow session started (data.childSessionId — attach to that session’s stream).
subagent.startedInline subagent execution began.
subagent.eventWraps a child stream event under data.event (inline subagents).
subagent.completedInline 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

EventMeaning
authorization.requiredConnection needs OAuth (data.name, data.description, data.authorization challenge with optional url, userCode, expiresAt, instructions, displayName).
authorization.completedAuthorization resolved (data.outcome: authorized | declined | failed | timed-out).
compaction.requestedContext compaction began (data.modelId, data.usageInputTokens).
compaction.completedCompaction 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:

Subagent call identifier. Remote subagent name. Outcome kind. Required for `session.completed`. Error payload for `session.failed`.

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.

{
  "revision": "<opaque-revision-id>"
}

:::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.

curl -X POST http://127.0.0.1:3000/eve/v1/dev/schedules/heartbeat
{
  "scheduleId": "heartbeat",
  "sessionIds": ["sess_1", "sess_2"]
}

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:

StatusTypical cause
400Malformed body, missing required fields, invalid startIndex
401 / 403Auth walk failed or forbidden
404Unknown session, schedule, or expired callback token
413 / 415Attachment exceeds size cap or violates media-type policy
500Handler 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.

# 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"

Wait for session.waiting, then continue:

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.\"}"
curl -N "http://127.0.0.1:3000/eve/v1/session/$SESSION_ID/stream?startIndex=42"

Related pages

Continuation tokens, stream reconnect, subagent child-session attachment, and delivery ordering. `eve/client`, streaming reducers, and React/Vue/Svelte hooks. Route auth strategies, env vars, and production deployment verification. OAuth flows, connection callbacks, and `authorization.required` stream events. Local and remote subagents, cron schedules, and dev schedule dispatch. End-to-end flow from `eve init` through first HTTP session.

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://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

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.

eve info

Or emit machine-readable output:

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.

Read each diagnostic’s message and sourcePath. Errors block compile; warnings are printed at build time but do not fail the build.

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.

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.

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 :::

ArtifactPathUse when
Discovery manifest.eve/discovery/agent-discovery-manifest.jsonConfirm a file was found on disk
Diagnostics.eve/discovery/diagnostics.jsonRead structured errors and warnings
Compiled manifest.eve/compile/compiled-agent-manifest.jsonSee what the runtime actually serves
Compile metadata.eve/compile/compile-metadata.jsonCheck status and diagnostic summary counts
Module map.eve/compile/module-map.mjsTrace 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:

Always `eve-discovery-diagnostics`. Schema version. Currently `1`. `errors` and `warnings` counts. Build fails when `errors > 0`; `compile-metadata.json` records `status: "failed"` in that case. 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
{
  "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"
    }
  ]
}

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:

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.

CodeSeverityTypical cause
discover/project-not-founderrorNo agent/ directory resolved from the app root
discover/required-instructions-missingerrorMissing instructions.md or instructions.ts
discover/deprecated-system-slotwarningFallback to deprecated system.{md,ts} — rename to instructions.*
discover/slot-collisionerrorSame slot has both markdown and module sources
discover/module-slot-collisionerrorMultiple module sources compete for one slot
discover/tool-name-invaliderroragent/tools/<name>.ts filename violates tool slug charset
discover/connection-name-invaliderrorConnection filename violates kebab-case slug rule
discover/channel-name-invaliderrorChannel filename or directory segment violates slug rule
discover/hook-name-invaliderrorHook filename or directory segment violates slug rule
discover/tools-directory-invaliderroragent/tools/ exists but is not a directory
discover/channels-directory-invaliderroragent/channels/ exists but is not a directory
discover/hooks-directory-invaliderroragent/hooks/ exists but is not a directory
discover/connections-directory-invaliderroragent/connections/ exists but is not a directory
discover/sandbox-directory-invaliderroragent/sandbox/ exists but is not a directory
discover/skills-directory-invaliderroragent/skills/ exists but is not a directory
discover/schedules-directory-invaliderrorRoot schedules/ exists but is not a directory
discover/unsupported-directorywarningIgnored directory under agent/
discover/skill-collisionerrorDuplicate skill name
discover/skill-markdown-missingerrorPackaged skill directory missing SKILL.md
discover/skill-frontmatter-invaliderrorInvalid YAML frontmatter in a skill
discover/connection-file-folder-collisionerrorBoth file and folder exist for one connection name
discover/connection-folder-emptyerrorConnection folder has no module source
discover/sandbox-folder-emptyerrorSandbox folder has no module source
discover/required-subagent-config-module-missingerrorSubagent missing required agent.ts
discover/local-subagent-schedules-invaliderrorSchedules under a subagent (root-only slot)
discover/schedule-file-unsupportederrorUnsupported file type under schedules/
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).

Common failure modes

SymptomLikely 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 shouldTighten the tool description and inputSchema; put procedural guidance in a skill, not the description. Confirm the tool appears in eve info.
Stuck on session.waitingTurn 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 routesExpected fail-closed auth. Replace placeholderAuth(), set VERCEL_PROJECT_ID, and configure environment so vercelOidc() accepts user tokens. See Auth and deployment.
Build fails with discovery errorsRead 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 runtimeMissing 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 2Configuration 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.

If another `eve dev` is already running for the same agent, startup exits with:
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.

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:

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).

LabelFix commandWhen it fires
model provider not linked/modelNo .vercel/project.json and no gateway credentials in env
AI Gateway credentials missing/modelProject is linked but neither AI_GATEWAY_API_KEY nor VERCEL_OIDC_TOKEN is set
not logged in/loginOff-critical-path vercel whoami probe reports logged out
Vercel CLI not found/vcvercel binary absent from PATH
External-provider models (routed outside the AI Gateway) skip gateway credential detections entirely — they use their own provider key and `/model` cannot reconfigure them.

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.

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 --> [*]
CascadeEventsSession state after
Terminalstep.failedturn.failedsession.failedDead — no further follow-up on the same continuation token
Recoverablestep.failedturn.failedsession.waitingParked — send another message with the current continuationToken
Success (conversation)turn.completedsession.waitingReady for next user message
Success (task)turn.completedsession.completedTerminal 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

CodeTerminal or recoverableMeaning
MODEL_CALL_FAILEDBoth — terminal for config errors; recoverable for transient provider failures in conversation modeModel call failed. Check details for gatewayName, statusCode, errorId. Gateway auth failures include GatewayAuthenticationError or summary name AI Gateway authentication failed.
WORKFLOW_STREAM_WRITE_FAILEDRecoverableDurable 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_FULFILLEDTerminal in task mode; recoverable in conversation modeAgent could not produce output matching outputSchema / final_output.
SESSION_FAILEDTerminalSession callback step failed.
REMOTE_AGENT_START_FAILEDTerminalRemote agent dispatch could not start.
REMOTE_AGENT_FAILEDTerminalRemote agent callback reported failure.
SUBAGENT_EXECUTION_FAILEDSurfaced on parent via action.resultDelegated 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:

CodeMeaning
TOOL_EXECUTION_DENIEDHITL approval denied or tool execution blocked
TOOL_EXECUTION_FAILEDTool returned an error-shaped JSON output ({ code, message })
ACTION_RESULT_FAILEDFallback when an action result is marked isError without a structured code

Continuation token errors

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 "".`

Failure event payload shape

{"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"}}

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:

SurfaceWhereWhat it shows
NDJSON streamGET /eve/v1/session/:sessionId/streamLive step.failed, turn.failed, session.failed, tool events
Workflow run tagsVercel Workflow dashboard ($eve.*)Session/turn tree, model id, token counts — no instrumentation.ts required
OpenTelemetryagent/instrumentation.tsAI SDK spans when configured

See Instrumentation and evals for tracing setup and workflow run tags.

Related pages

All `eve` commands, flags, exit codes, and the edit-info-dev-build-start loop. Scaffold, verify discovery with `eve info`, and iterate with `eve dev`. `continuationToken` contracts, NDJSON events, and reconnect behavior. Stable `/eve/v1` routes and stream event vocabulary. Route auth, env vars, and production verification. Authored slots, path-derived naming, and what compiles into `.eve/`.

Similar Articles