@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…
Summary
Vercel released Eve, an open-source filesystem-first durable backend agent framework, with comprehensive documentation available.
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
- 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.
- 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.
- QuickstartScaffold with eve init, verify discovery with eve info, iterate with eve dev, and create/stream/continue an HTTP session against /eve/v1/session.
- Project layoutAuthored slots under agent/, path-derived naming (no name fields), evals/ placement, subagent inheritance rules, and what compiles into .eve/ artifacts.
- Execution model and durabilitySession/turn/step nesting, durable workflow checkpoints, crash resume semantics, parked work (HITL, OAuth, subagents), and message delivery constraints on continuationToken.
- Sessions and streamingcontinuationToken vs sessionId contracts, POST /eve/v1/session and follow-up routes, NDJSON stream events, reconnect behavior, and subagent child-session attachment.
- 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.
- Context controlAlways-on instructions vs on-demand skills, workspace visibility through sandbox tools, dynamic capabilities (defineDynamic), and subagent context isolation.
- Security modelApp runtime vs sandbox trust boundaries, secret brokering, connection token handling, channel signature verification, and fail-closed route auth defaults.
- 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.
- ToolsdefineTool schema and execute(ctx), HITL approval predicates (always/once/never), tool auth and requireAuth, built-in overrides, and dynamic tool resolution.
- 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/)
:::
| Path | Resolves to | Author with |
|---|---|---|
agent/tools/get_weather.ts | tool get_weather | defineTool from eve/tools |
agent/connections/linear.ts | connection linear | defineMcpClientConnection or defineOpenAPIConnection from eve/connections |
agent/skills/summarize.md | skill summarize | markdown file (loaded via load_skill) |
agent/channels/slack.ts | channel slack | defineChannel or platform factory from eve/channels/* |
agent/subagents/researcher/ | subagent researcher | nested agent.ts + authored slots |
The root agent name comes from package.json name, falling back to the app-root directory name. Local subagents take their name from their directory. Add a file and Eve discovers it; rename or move it and its identity moves with it. Run eve info to inspect the resolved surface and .eve/discovery/diagnostics.json when discovery fails.
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
| Layer | Responsibility | Owns |
|---|---|---|
| Channel | Normalize inbound transport, apply auth and delivery policy, start or resume sessions | continuationToken |
| Harness | Run one unit of AI work (model step, tool calls, compaction) and return { session, next } | Turn/step execution inside the agent loop |
| Runtime | Persist session state, follow next, stream events, orchestrate workflow primitives | sessionId, NDJSON stream, durable checkpoints |
Sessions nest as session → turn → step. Turns checkpoint at step boundaries via the Workflow SDK, so crash, timeout, and redeploy resume from the last completed step. Parked work (HITL approval, OAuth, subagents) suspends the workflow until input arrives. The default harness ships built-in tools (bash, file ops, web_fetch, todo, ask_question, agent, load_skill, connection_search) without any imports.
Public entry points
Eve exposes three surfaces for authoring, operating, and integrating agents.
eve CLI
The eve binary runs from the app root and loads .env/.env.local before every command. Running eve with no subcommand starts eve dev.
| Command | Purpose |
|---|---|
eve init [target] | Scaffold a new agent or add one to an existing project |
eve info | Print discovered tools, skills, routes, artifacts, and diagnostics |
eve build | Compile .eve/ artifacts and build host output |
eve start | Serve the built .output/ app |
eve dev | Start local runtime and open the interactive terminal UI |
eve link / eve deploy | Link to Vercel and deploy |
eve eval | Run evals locally or against a remote URL |
eve channels add/list | Scaffold or list authored channels |
Recommended loop: edit authored files → eve info to verify discovery → eve dev to iterate → eve build → eve start for production.
/eve/v1 HTTP routes
All stable runtime transport routes share the EVE_ROUTE_PREFIX of /eve/v1.
| Method | Route | Purpose |
|---|---|---|
GET | /eve/v1/health | Health check |
GET | /eve/v1/info | JSON inspection payload for the current agent |
POST | /eve/v1/session | Create a new durable session |
POST | /eve/v1/session/:sessionId | Continue a session with continuationToken |
GET | /eve/v1/session/:sessionId/stream | NDJSON event stream |
GET | /eve/v1/connections/:name/callback/:token | OAuth callback (unguessable token) |
GET | /eve/v1/callback/:token | Terminal session callback |
Dev-only routes (/eve/v1/dev/schedules/:scheduleId, /eve/v1/dev/runtime-artifacts) register only when Nitro runs in dev mode.
:::endpoint POST /eve/v1/session
Create a durable session. Returns sessionId and continuationToken in the JSON body; the x-eve-session-id header names the session to stream.
:::
define* authoring API
Authoring helpers live in packages/eve/src/public/ and ship through the eve package exports.
| Import path | Helper | Authored slot |
|---|---|---|
eve | defineAgent, defineRemoteAgent | agent/agent.ts |
eve/instructions | defineInstructions, defineDynamic | agent/instructions.ts or agent/instructions/ |
eve/tools | defineTool, defineBashTool, defineReadFileTool, … | agent/tools/*.ts |
eve/skills | defineDynamic | dynamic skill modules |
eve/connections | defineMcpClientConnection, defineOpenAPIConnection | agent/connections/*.ts |
eve/channels | defineChannel | agent/channels/*.ts |
eve/sandbox | defineSandbox | agent/sandbox.ts or agent/sandbox/sandbox.ts |
eve/schedules | defineSchedule | agent/schedules/*.ts or *.md |
eve/hooks | defineHook | agent/hooks/*.ts |
eve/context | defineState | session-scoped durable state |
eve/instrumentation | defineInstrumentation | agent/instrumentation.ts (root-only) |
eve/evals | defineEval, defineEvalConfig | evals/ at app root |
Tool and connection handlers receive a ctx with ctx.session, ctx.getSandbox(), ctx.getSkill(), ctx.getToken(), and ctx.requireAuth(). Client integration ships separately via eve/client, eve/react, eve/vue, eve/svelte, and framework plugins eve/next, eve/nuxt, eve/sveltekit.
Shortest path to a running session
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/...).
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"}'
Compiled artifacts
eve build and eve dev write inspectable output under .eve/:
| Artifact | Contents |
|---|---|
.eve/discovery/agent-discovery-manifest.json | Filesystem discovery results |
.eve/discovery/diagnostics.json | Authored-shape errors and warnings |
.eve/compile/compiled-agent-manifest.json | Serialized surface Eve loads at runtime |
.eve/compile/module-map.mjs | Compiled module entrypoints |
On Vercel (VERCEL=1), eve build also writes .vercel/output. Local eve build skips that bundle but still produces .eve/ artifacts.
Next
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.mdxpackages/eve/package.jsonpackages/eve/bin/eve.js.nvmrcpackages/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"
}
}
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:
| Package | Role |
|---|---|
eve | Framework, CLI binary, compiled runtime |
ai | AI SDK model calls and streaming |
zod | Tool and schema validation |
@vercel/connect | Connection helpers (scaffold only) |
The eve package ships with a single runtime dependency (nitro). Peer dependencies such as framework plugins (next, nuxt, sveltekit) and optional integrations are installed only when you add them.
Once eve is installed, the full documentation bundle is available locally at node_modules/eve/docs/.
The eve binary
The npm package exposes a CLI binary mapped to bin/eve.js:
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:
| Credential | Environment variable | How to obtain |
|---|---|---|
| API key | AI_GATEWAY_API_KEY | Create a key in the Vercel AI Gateway dashboard, or paste one in the dev TUI /model flow |
| OIDC token | VERCEL_OIDC_TOKEN | Run eve link to link a Vercel project and pull preview environment variables into .env.local |
When both are present, AI_GATEWAY_API_KEY takes precedence over VERCEL_OIDC_TOKEN, matching the AI SDK gateway provider behavior.
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 pattern | Environment variable |
|---|---|
anthropic/claude-... | ANTHROPIC_API_KEY |
openai/gpt-... | OPENAI_API_KEY |
google/gemini-... | GOOGLE_API_KEY |
The prefix before the first / determines the variable name: uppercase slug with _API_KEY appended (for example, anthropic/claude-sonnet-4.6 → ANTHROPIC_API_KEY).
To use a direct provider SDK model instead of a gateway string id, change agent/agent.ts:
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:
.env.development.local.env.local.env.development.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
| Symptom | Likely cause | Fix |
|---|---|---|
Eve requires Node.js >=24 | Node version too old | Install Node 24+ and retry |
| Gateway “no credentials” error | Missing AI_GATEWAY_API_KEY and VERCEL_OIDC_TOKEN | Run eve link or set AI_GATEWAY_API_KEY in .env.local |
| Stale gateway key shadows OIDC | Shell exports an old AI_GATEWAY_API_KEY | Unset the shell variable or update the key |
| Provider key missing for direct model | External provider without env var | Set the provider’s *_API_KEY in .env.local |
| Env changes not picked up | Running outside eve dev | Restart the process, or use eve dev which hot-reloads env files |
For deeper diagnostics, see Troubleshooting.
Next
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.mdxpackages/eve/src/cli/commands/init.tsapps/fixtures/weather-agent/agent/agent.tsapps/fixtures/weather-agent/agent/tools/get_weather.tsapps/fixtures/weather-agent/agent/instructions.mdpackages/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
- Node 24 or newer
- A package manager (
npm,pnpm,yarn, orbun) - 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, andagent/channels/eve.ts - Pins
eve,ai,zod, and@vercel/connectinpackage.json - Adds
dev,build, andstartscripts that call theevebinary - Installs dependencies and initializes Git (fresh scaffolds only)
- Starts
eve dev --input /modelin 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.
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:
| Label | Example value |
|---|---|
| Create | POST /eve/v1/session |
| Continue | POST /eve/v1/session/:sessionId |
| Stream | GET /eve/v1/session/:sessionId/stream |
For machine-readable output:
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.
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
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.
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. :::
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.
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. :::
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:
| Event | When |
|---|---|
session.started | Workflow session begins |
turn.started | Turn boundary |
message.received | User message accepted |
actions.requested | Harness requests tool calls (for example get_weather) |
action.result | Tool result projected back |
message.completed | Final assistant text for the step |
session.waiting | Session parked for the next user message |
session.completed | Terminal success |
message.appended and reasoning.appended are optional incremental events; clients that only need final text can wait for message.completed. Human-in-the-loop, authorization, and failure events (input.requested, authorization.required, turn.failed, session.failed) appear when the harness parks for external input.
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. :::
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
| Surface | Role |
|---|---|
agent/instructions.md | Always-on behavior |
agent/tools/*.ts | Typed capabilities the model invokes |
agent/channels/eve.ts | HTTP ingress for sessions |
eve dev | Local host + optional TUI |
/eve/v1/session* | Create, stream, and continue durable conversations |
The agent already runs with the default harness (bash, file ops, web_fetch, todo, ask_question, agent, load_skill, connection_search). Override or disable built-ins under agent/tools/ when you need tighter control.
Next
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.mdpackages/eve/src/compiler/manifest.tspackages/eve/src/compiler/normalize-manifest.tspackages/eve/src/compiler/artifacts.tspackages/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:
| Root | Role |
|---|---|
| App root | The enclosing project directory (marked by package.json or vercel.json) |
| Agent root | The directory Eve walks for authored slots |
Eve supports two layouts:
| Layout | Agent root | App root | When to use |
|---|---|---|---|
| Nested (recommended) | <app-root>/agent/ | Parent of agent/ | Keeps app scaffolding separate from the authored surface |
| Flat | Same as app root | Same directory | Supported when the app root is also the agent root |
Nested layout is detected when the app root contains an agent/ directory. Flat layout is detected when the current directory contains recognized agent-root entries (agent.ts, instructions.md, tools/, and similar) without a separate agent/ folder.
Path-derived naming
Identity comes from the path. Authored define* exports do not carry name or id fields — the compiler derives identifiers from filenames and directory names.
| Authored path | Runtime identifier |
|---|---|
agent/tools/get_weather.ts | tool get_weather |
agent/tools/billing/refund.ts | tool billing-refund (nested segments flattened with -) |
agent/connections/linear.ts | connection linear |
agent/skills/summarize.md | skill summarize |
agent/subagents/researcher/agent.ts | subagent tool researcher |
agent/subagents/linear.ts (with defineRemoteAgent) | remote subagent tool linear |
Root agent name comes from package.json name (npm scope stripped), falling back to the app-root directory basename when name is absent.
Local subagent name comes from the directory name (subagents/<id>/) or the module basename for single-file subagents (subagents/<id>.ts).
Subagent tool names share the same runtime namespace as authored tools. A subagent named researcher collides with a tool named researcher; Eve rejects the build rather than picking a winner.
Recommended layout
:::files my-agent/ ├── package.json ├── tsconfig.json ├── agent/ │ ├── agent.ts │ ├── instructions.md │ ├── instrumentation.ts │ ├── channels/ │ ├── connections/ │ ├── hooks/ │ ├── skills/ │ ├── lib/ │ ├── sandbox/ │ ├── tools/ │ ├── schedules/ │ └── subagents/ └── evals/ ├── evals.config.ts └── smoke.eval.ts :::
evals/ lives at the app root as a sibling of agent/, not inside it. Eval files are discovered at eve eval time and are not written into .eve/.
Authored slot table
The Subagents column states whether a local subagent package (subagents/<id>/) may author the slot. Unsupported directories under an agent root are ignored with a discovery warning.
| Path | Description | Subagents | Notes |
|---|---|---|---|
agent.ts | Runtime config (defineAgent) | Yes | Model, compaction, build.externalDependencies, experimental. Required description on subagents. |
instructions.md / instructions.ts / instructions/ | Base system prompt | Optional | Flat file or directory of .md/.ts sources. Static sources compose at build time; defineDynamic + defineInstructions resolve at runtime. Required on root, optional on subagents. |
instrumentation.ts | Telemetry config | No | OTel exporter and AI SDK span settings. Auto-discovered at server startup before agent code. Root-only. |
channels/ | HTTP and messaging entrypoints | No | Module-backed only. Recursive directories supported. Root-only. |
connections/ | MCP and OpenAPI connections | Yes | One connection per file or folder (connections/<name>/connection.ts). Name derived from path segment. |
hooks/ | Lifecycle and stream-event subscribers | Yes | Module-backed only. Recursive directories supported. |
skills/ | On-demand procedures and capability packs | Yes | Flat markdown, module-backed skills, or packaged skills. Seeded into /workspace/skills/.... |
lib/ | Shared authored helper code | Yes | Import-only; never mounted into the sandbox workspace. |
sandbox.ts or sandbox/sandbox.ts | Single sandbox definition | Yes | Top-level sandbox.ts for definition-only override; sandbox/sandbox.ts + sandbox/workspace/** to also seed files. Framework default applies when neither is authored. |
sandbox/workspace/** | Files seeded into the sandbox | Yes | Mirrored into /workspace/... at session bootstrap. Cannot contain skills/ (reserved). |
tools/ | Typed executable integrations | Yes | Module-backed only. Recursive directories supported; nested paths flatten into tool names. |
schedules/ | Recurring jobs | No | <name>.ts (defineSchedule) or <name>.md (frontmatter cron: + prompt body). Recursive nesting supported. Root-only. |
subagents/ | Specialist child agents | Yes | Each child is a local package under subagents/<id>/, a single-file module, or a defineRemoteAgent module. Nested subagents supported. |
What reaches the sandbox workspace
Eve does not mount the entire agent/ tree into the sandbox. Only two authored sources land in the live workspace:
| Authored source | Sandbox path |
|---|---|
agent/skills/** | /workspace/skills/... |
agent/sandbox/workspace/** | /workspace/... at session bootstrap |
Everything under lib/ stays import-only source code and never reaches the workspace. The compiled manifest records a workspaceResourceRoot descriptor pointing at materialized trees under .eve/compile/workspace-resources/.
Local subagent layout and inheritance
A declared local subagent lives under agent/subagents/<id>/ and uses the same slot grammar as the root, minus root-only slots.
:::files agent/subagents/researcher/ ├── agent.ts # required; must export description ├── instructions.md # optional ├── connections/ ├── hooks/ ├── skills/ ├── lib/ ├── sandbox/ ├── tools/ └── subagents/ # nested subagents supported :::
Inheritance rule: A declared subagent inherits nothing from the root’s authored slots. Discovery treats its directory as its own agent root. An absent slot falls back to the framework default, not to the root’s version.
| Slot | Declared subagent behavior |
|---|---|
| Instructions | Own instructions.{md,ts} or directory; optional |
| Tools, connections, skills, hooks | Own directories only |
| Sandbox | Own sandbox.ts or sandbox/; else framework default (not parent’s sandbox) |
| Channels, schedules, instrumentation | Not supported — discovery emits an error for schedules/ inside subagents |
State (defineState) | Fresh per child session; never shared |
When two subagents need the same procedure, copy skill markdown into each skills/ directory, or share typed helpers via lib/. The built-in agent tool is the exception: its children are copies of the parent and share sandbox, tools, and connections.
Remote subagents (defineRemoteAgent) also live under agent/subagents/ as single-file modules. Identity is path-derived like local subagents; the compiler lowers them into the manifest’s remoteAgents array.
Evals placement
Evals are authored at the app root, outside agent/:
| File pattern | Purpose |
|---|---|
evals/**/*.eval.ts | Eval definitions (defineEval default export) |
evals/evals.config.ts | Required run-wide defaults (defineEvalConfig) |
Eval identity is path-derived: evals/weather/brooklyn-forecast.eval.ts becomes id weather/brooklyn-forecast. Array exports in one file receive zero-padded index suffixes (weather/0000). Eval discovery runs independently of eve build and does not produce .eve/ artifacts.
Compile pipeline and .eve/ artifacts
eve build (and dev-server startup) runs discovery, then writes compiler-owned artifacts. The pipeline is: resolve project layout → walk agent/ → emit discovery manifest → compile normalized manifest → materialize workspace resources.
flowchart LR
subgraph authored [Authored surface]
A["agent/**"]
end
subgraph discovery [".eve/discovery/"]
D1["agent-discovery-manifest.json"]
D2["diagnostics.json"]
end
subgraph compile [".eve/compile/"]
C1["compiled-agent-manifest.json"]
C2["module-map.mjs"]
C3["compile-metadata.json"]
C4["workspace-resources/"]
C5["channel-instrumentation-types.d.ts"]
end
A --> D1
A --> D2
D1 --> C1
C1 --> C2
C1 --> C4
Discovery artifacts (.eve/discovery/)
| File | Contents |
|---|---|
agent-discovery-manifest.json | Raw discovery manifest: source refs, logical paths, subagent graph before module import |
diagnostics.json | Structured discovery diagnostics with error and warning counts |
Compile artifacts (.eve/compile/)
| File | Contents |
|---|---|
compiled-agent-manifest.json | Versioned runtime manifest (eve-agent-compiled-manifest, schema version 29): normalized config, tools, connections, skills, channels, schedules, hooks, sandbox metadata, flattened subagent nodes and edges, remoteAgents, workspaceResourceRoot |
module-map.mjs | Compiled module map for loading authored exports at runtime |
compile-metadata.json | Generator version, artifact SHA-256 digests, status (ready or failed), discovery summary |
workspace-resources/ | Per-node materialized skill packages and sandbox workspace trees |
channel-instrumentation-types.d.ts | Generated channel instrumentation types |
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
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.mdpackages/eve/src/execution/workflow-runtime.tspackages/eve/src/execution/durable-session-migrations/chain.tspackages/eve/src/execution/next-driver-action.tspackages/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.
| Level | Scope | Runtime owner | Typical stream events |
|---|---|---|---|
| Session | Whole durable conversation or task | workflowEntry driver workflow | session.started, session.waiting, session.completed, session.failed |
| Turn | One inbound delivery and all work it triggers until the agent responds | turnWorkflow child workflow | turn.started, turn.completed, turn.failed |
| Step | One model call and the tool calls it makes | turnStep ("use step" boundary) | step.started, step.completed, step.failed |
Within a turn, the harness tool loop may run multiple steps. StepNext tells the runtime what happens after each step:
- A
StepFnreference — 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
Durable checkpoints and wire migrations
Session-mutating steps return DurableSessionState as the atomic persistence boundary. The state carries:
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.
After a recoverable step or turn failure, Eve emits step.failed → turn.failed → session.waiting (conversation mode) so the session parks and accepts a follow-up delivery. Structural failures emit session.failed instead; the continuation token is then dead.
Driver action contract
Each turn resolves to a NextDriverAction the driver dispatches on. This is a closed contract — adding a new kind is breaking for pinned drivers.
kind | When emitted | Driver behavior |
|---|---|---|
done | Turn produced terminal output | Fire session callback, notify delegated parent, exit driver |
park | Turn waiting on human input, OAuth, or adapter-only delivery | Register park hook, emit session.waiting, block until resumeHook |
dispatch-runtime-actions | Pending subagent or remote-agent calls | Start child sessions, wait for runtime-action-result deliveries |
dispatch-code-mode-runtime-actions | Code-mode runtime action interrupt | Dispatch code-mode children, same wait pattern |
The turn workflow chooses park vs dispatch-runtime-actions at the park boundary:
- Pending runtime actions (subagent calls) →
dispatch-runtime-actions - Pending OAuth (
authorizationNames) →parkon the auth hook ({sessionId}:auth) - Pending HITL input batch with
capabilities.requestInput === true→park mode === "conversation"with no pending work →park(between-turn wait)mode === "task"with no pending work and no parkable state → throws (Task mode cannot wait for follow-up input)
Task-mode scheduled chains therefore must finish in one invocation; they emit session.completed instead of session.waiting at turn epilogue.
Parked work
When work must wait, the driver suspends the workflow and holds no compute until input arrives.
HITL (tool approval and ask_question)
Tool approval gates and ask_question register only when SessionCapabilities.requestInput is true. Channel routes that can reach a human (HTTP, Slack, etc.) set this at session creation; scheduled task routes omit it.
When the harness parks on a pending input batch, the turn emits turn.completed then session.waiting with data.wait: "next-user-message". Clients deliver inputResponses on the next POST to the session continue route.
Subagent HITL is proxied through the parent: a child sends subagent-input-request on the parent’s hook; the parent re-emits input.requested and routes inputResponses back to the child’s continuationToken via routeProxiedDeliverStep.
OAuth and connection authorization
When a tool calls requireAuth and authorization is pending, the turn parks with authorizationNames. The driver waits on a separate auth hook at {sessionId}:auth, independent of the park continuationToken. OAuth callback URLs embed this token so channel re-keying mid-turn does not invalidate callbacks.
Deliveries carrying authorizationCallback in the payload complete pending challenges; authorization.completed events emit before the harness resumes.
Subagents and remote agents
When the agent tool (or code-mode equivalent) delegates work, the turn parks with dispatch-runtime-actions. dispatchRuntimeActionsStep starts each child as its own workflowEntry run, records the child’s continuationToken on the parent session, and emits subagent.called on the parent stream. The driver accumulates runtime-action-result deliveries before dispatching the next turn.
Each subagent gets its own durable session, sandbox, skills, and state. Nothing crosses the boundary implicitly; the parent receives results through the runtime-action channel.
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.
Deliver and resume-or-start
Runtime.deliver() calls resumeHook(continuationToken, hookPayload). If no hook matches, Eve throws RuntimeNoActiveSessionError (code: "NO_ACTIVE_SESSION"). Callers using the resume-or-start pattern treat this as the signal to start a fresh session.
The park hook is registered before the first turn completes so fast clients cannot POST follow-ups before the hook exists.
Coalescing and ordering constraints
Eve does not maintain a durable per-session message queue.
| Situation | Behavior |
|---|---|
Session parked (session.waiting) | One delivery to continuationToken wakes the session and starts the next turn |
| Turn already active | The hook may accept additional deliveries, but the driver drains them only at specific workflow boundaries |
| Multiple deliveries ready at drain time | Eve may coalesce them into one turn (coalesceDeliveries); drain is best-effort and timing-dependent |
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
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
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.mdpackages/eve/src/protocol/routes.tspackages/eve/src/client/open-stream.tspackages/eve/src/client/ndjson.tspackages/eve/src/client/session.tspackages/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
| Handle | Role | Used for |
|---|---|---|
continuationToken | Resume handle for the session’s current workflow hook | POST follow-ups (message, inputResponses) while the session is parked or active |
sessionId | Stream-and-inspect handle (workflow run id) | GET /eve/v1/session/:sessionId/stream, x-eve-session-id response header |
A session has one active continuation at a time. Channels may re-key the token mid-session (for example Slack anchoring a thread ts via ctx.session.setContinuationToken). Deliveries to a superseded token after re-key are silently dropped. A stale token with no matching parked hook yields RuntimeNoActiveSessionError; the HTTP send path may fall back to starting a new session for plain messages, but rejects inputResponses when the target session cannot be found.
Session routes
The default eveChannel registers three routes under /eve/v1. All run the channel’s auth chain via routeAuth before dispatch.
| Method | Path | Status | Purpose |
|---|---|---|---|
POST | /eve/v1/session | 202 | Create a session and start the first turn |
POST | /eve/v1/session/:sessionId | 200 | Continue a session with continuationToken |
GET | /eve/v1/session/:sessionId/stream | 200 | Stream NDJSON lifecycle events |
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.
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.
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"
}
:::endpoint GET /eve/v1/session/:sessionId/stream Replay or tail the durable NDJSON event stream for one session.
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:
| Event | Meaning |
|---|---|
session.waiting | Session parked; safe to send the next follow-up with the current continuationToken |
session.completed | Terminal success |
session.failed | Terminal failure; carries { code, message, details? } |
ClientSession and openStreamIterable stop a per-turn consumer at the first session.completed, session.failed, or session.waiting event (isCurrentTurnBoundaryEvent).
Lifecycle events
| Event | Meaning |
|---|---|
session.started | Durable session created; child runs include data.invocation (kind: "subagent", parent ids) |
turn.started | New turn; data.turnId, data.sequence |
message.received | Inbound user message accepted |
step.started | Model step began |
actions.requested | Tool call batch requested (data.actions) |
action.result | Tool result projected (data.status: completed | failed) |
input.requested | HITL pause; data.requests |
step.completed | Model step finished; data.finishReason, optional data.usage |
step.failed | Step failure; { code, message, details? } |
turn.completed | Turn succeeded |
turn.failed | Turn failure; { code, message, details? } |
result.completed | Structured output for schema turns; data.result |
compaction.requested / compaction.completed | Context compaction checkpoint |
authorization.required / authorization.completed | Connection OAuth challenge and outcome |
Streaming text and reasoning
| Event | Meaning |
|---|---|
message.appended | Assistant text delta; messageDelta + cumulative messageSoFar |
message.completed | Finalized assistant text block; data.finishReason |
reasoning.appended | Reasoning delta; reasoningDelta + reasoningSoFar |
reasoning.completed | Finalized reasoning block |
message.completed may fire multiple times per turn. finishReason: "tool-calls" marks interim narration before a tool call; other values mark a terminal assistant message for that step. Pair deltas (*.appended) with finalized blocks (*.completed) when building UIs.
Subagent events on the parent stream
| Event | Meaning |
|---|---|
subagent.called | Workflow subagent delegated; includes data.childSessionId for child stream attachment |
subagent.started | Inline subagent execution began |
subagent.event | Wraps one child stream event under data.event (inline path) |
subagent.completed | Inline subagent finished; data.output |
Message delivery constraints
continuationToken names the session’s workflow hook, not a durable FIFO message queue. When the session is waiting, one delivery wakes the next turn. Concurrent sends while a turn is active are best-effort at workflow boundaries and may be folded together.
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 heredata.callId,data.toolName,data.namedata.sessionId,data.turnId,data.workflowIddata.remote.urlwhen delegating to adefineRemoteAgenttarget
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:
- Channel adapter handler (
adapter[event.type]) — side effects only; event is not transformed - Adapter state persisted to context
- Event written to the durable stream (with
meta.at) - Authored hooks (
dispatchStreamEventHooks) - Dynamic tool, skill, and instruction resolvers
Channel metadata projected from adapter state is current before hooks and resolvers read ctx.channel.
Related pages
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.mdpackages/eve/src/public/tools/index.tspackages/eve/src/compiler/normalize-agent-config.tspackages/eve/src/execution/sandbox/bash-tool.tspackages/eve/src/execution/sandbox/read-file-tool.tsdocs/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
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:
- Assembles the effective toolset (
buildToolSetWithProviderTools), including dynamic tools resolved atstep.startedand provider-managed replacements forweb_search. - Runs the model through
ToolLoopAgent, executing tool calls until the turn completes, parks for HITL/OAuth/subagent input, or hits a terminal error. - Evaluates compaction when estimated input tokens exceed the session threshold; on compaction, calls
preserveFrameworkStateOnCompactionto reset read-before-write stamps and re-inject the active todo list. - Emits NDJSON stream events (
step.started, tool results,compaction.requested,compaction.completed,input.requested, and terminal events).
Tools without a local execute function (notably ask_question) are client-side: the model can call them, but the runtime parks until the channel delivers user input.
Compaction defaults
Compaction is on by default for every session. Configuration is authored in agent/agent.ts via defineAgent({ compaction: { ... } }) and compiled into the agent manifest.
| Setting | Default | Behavior |
|---|---|---|
compaction.thresholdPercent | 0.9 | Fraction of the primary model context window that triggers summarization |
| Recent window | 10 messages | Harness keeps the tail of history verbatim; older messages are summarized |
| Compaction summary model | Active turn model | Override with compaction.model to use a different model for summaries only |
| Fallback threshold | 100_000 tokens | Used when context-window metadata is unavailable at session creation |
When compaction runs, the harness calls a dedicated summarizer (compactMessages) with a fixed system prompt, then appends the summary as a compact checkpoint message. Framework-owned state is preserved automatically—there is no per-tool compaction hook:
- Read-before-write tracking is cleared so a subsequent
write_filemust 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.
| Tool | Purpose | Where it runs |
|---|---|---|
bash | Run a shell command in the workspace | Sandbox (proxied via executeBashOnSandbox) |
read_file | Read a text file with line-numbered output; records read stamps | Sandbox FS |
write_file | Write a complete file; enforces read-before-write and stale-read detection | Sandbox FS |
glob | Find files by glob pattern | Sandbox FS |
grep | Search file contents by regex | Sandbox FS |
web_fetch | Fetch a URL (markdown/text/html) | App runtime |
web_search | Search the web via the model provider | Provider (no local execute; injected per step) |
todo | Maintain a durable per-session todo list | App runtime (TodoStateKey) |
ask_question | Ask the user a clarifying question mid-turn; parks until answered | App runtime (client-side, no execute) |
agent | Delegate a subtask to a copy of the current agent | App runtime (subagent-call runtime action) |
load_skill | Pull on-demand skill instructions into the current turn | App runtime (reads SKILL.md from sandbox) |
connection__search | Discover tools across declared connections | App runtime (dynamic resolver, connections only) |
Sandbox and file tools
Shell and filesystem tools (bash, read_file, write_file, glob, grep) are defined in the app runtime but proxy work into the agent’s single sandbox session. bash tail-truncates stdout/stderr to shared output limits. read_file defaults to offset: 1, limit: 2000 lines, rejects binary files (NUL bytes), and persists a full-file stamp for write_file stale-read detection.
web_search
The framework registers web_search with a throwing local stub. At step time, buildToolSetWithProviderTools replaces it with the real provider tool when the active model supports a known backend (Anthropic, OpenAI, Google, or AI Gateway/Perplexity). Override with defineTool() in agent/tools/web_search.ts to supply your own executor—the override’s execute prevents provider injection.
ask_question
ask_question has no execute method. The harness exposes it only when SessionCapabilities.requestInput is true (interactive sessions with HITL input). Scheduled task roots and subagent chains without that capability never see the tool. Input schema: { prompt, options?, allowFreeform? }; the turn parks until the channel delivers an answer.
agent (self-delegation)
Unless a subagent tool already occupies the name, the harness injects an agent tool that launches a copy of the current agent on a focused subtask. The child inherits the same tools, connections, and instructions but starts with fresh conversation history and fresh state. For self-delegation (subagentName: "agent"), the child shares the parent’s sandbox filesystem—writes remain visible to the parent.
Input schema (all subagent tools):
{
"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, optionalconnection, optionallimit)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.
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.
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
| Goal | Action |
|---|---|
| Same capability, different behavior (logging, guards, backend) | Override — same slug, spread default from eve/tools/defaults |
| Remove a capability entirely (lock down shell or web fetch) | Disable — disableTool() sentinel |
| New capability the harness does not ship | Author — new slug under agent/tools/ |
Experimental Workflow tool
The Workflow tool is shipped but off by default. Enable it by re-exporting the opt-in marker from agent/tools/workflow.ts:
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
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.mdpackages/eve/src/compiler/normalize-instructions.tspackages/eve/src/compiler/normalize-skill.tspackages/eve/src/execution/skills/instructions.tsdocs/guides/dynamic-capabilities.mdpackages/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/)
Always-on instructions
Instructions are the permanent system prompt. Eve prepends them to every model call in a session.
| Source | When resolved | Runtime behavior |
|---|---|---|
agent/instructions.md | Build time | Markdown captured into compiled manifest |
agent/instructions.ts with defineInstructions | Build time (once) | Module runs once at compile; resulting markdown is frozen in manifest |
agent/instructions/ directory | Build time | Non-recursive; entries compose in filename order after any root instructions.md |
agent/instructions/*.ts with defineDynamic | Runtime | Resolver runs on session.started and turn.started |
Static module-backed instructions execute once at build time. There is no per-session re-evaluation for defineInstructions exports.
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.
Loading a skill adds instructions only. It does not register new tools. Typed runtime behavior still comes from agent/tools/.
Skill authoring shapes
| Shape | Path | Seeded to workspace |
|---|---|---|
| Flat markdown | agent/skills/<name>.md | /workspace/skills/<name>/SKILL.md |
| Packaged directory | agent/skills/<name>/SKILL.md + siblings | Full directory under /workspace/skills/<name>/ |
| Module-backed | agent/skills/<name>.ts with defineSkill | Generated SKILL.md and files entries |
| Dynamic | agent/skills/<name>.ts with defineDynamic | Resolved at runtime, materialized to sandbox |
Packaged sibling files (references/, assets/, scripts/) are not pasted into the prompt. The model inspects them with bash, read_file, glob, or grep after loading the skill.
At build time, materializeWorkspaceResources copies authored skills and sandbox workspace seeds into .eve/compile/workspace-resources/<nodeId>/. Eve seeds that tree into the runtime sandbox at session bootstrap. A sandbox workspace cannot define a skills/ entry — Eve manages that path.
Workspace visibility through sandbox tools
Eve does not inline the full authored tree into the prompt. Only two sources reach the runtime workspace:
agent/skills/→/workspace/skills/...agent/sandbox/workspace/**→/workspace/...
Everything under lib/ stays import-only and never mounts to the workspace.
The workspace section in the system prompt is a shallow overview: the live root (/workspace), sorted root entries, and guidance to verify paths with bash before claiming file availability. Deeper inspection is explicit tool work.
| Tool | Sandbox scope | Typical use |
|---|---|---|
bash | /workspace cwd | ls, find, rg, shell commands |
read_file | Sandbox FS | Line-numbered text reads |
glob | Sandbox FS | Pattern-based file discovery |
grep | Sandbox FS | Regex search across files |
write_file | Sandbox FS | File writes (read-before-write enforced) |
Tools and load_skill proxy through the app runtime into the agent’s single sandbox. load_skill itself runs in the app runtime but reads skill files from the sandbox.
Dynamic capabilities with defineDynamic
When context depends on caller identity, tenant, channel metadata, or external data, use defineDynamic instead of static authoring. Import paths:
| Capability | Import | Authored under |
|---|---|---|
| Instructions | defineDynamic, defineInstructions from eve/instructions | agent/instructions/ |
| Skills | defineDynamic, defineSkill from eve/skills | agent/skills/ |
| Tools | defineDynamic, defineTool from eve/tools | agent/tools/ |
Resolvers receive a DynamicResolveContext with ctx.session.id, ctx.session.auth, ctx.channel metadata, and conversation messages.
Resolver events
| Event | Dynamic instructions | Dynamic skills | Dynamic tools |
|---|---|---|---|
session.started | Yes | Yes | Yes |
turn.started | Yes | Yes | Yes |
step.started | No | No | Yes |
Instructions and skills are restricted to session and turn boundaries because they feed the cache-sensitive system prompt. Dynamic tools can also resolve before each model call.
On a matching event, execution order is: channel adapter handler → stream-event hooks → dynamic resolvers. The tool loop reads the current tool set immediately before each model call.
Dynamic instructions example
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.
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
defineStatestate - Child file writes are immediately visible to the parent
The parent packs everything the child needs into message. The child never sees parent history.
Declared subagents
A declared subagent under agent/subagents/<id>/ is discovered as its own agent root. It inherits nothing from the root’s authored slots.
| Slot | Built-in agent tool | Declared subagent |
|---|---|---|
| Instructions | Copy of parent | Own instructions.{md,ts}, optional |
| Tools | Inherited | Own tools/ |
| Connections | Inherited | Own connections/ |
| Skills | Inherited | Own skills/ (invisible to parent) |
| Sandbox | Shared with parent | Own sandbox/ or framework default |
| Hooks | Inherited | Own hooks/ (parent hooks do not fire) |
| State | Fresh | Fresh |
| History | Fresh child session | Fresh child session |
Each delegation spins up a child session and stream. The parent emits subagent.called with childSessionId; subscribe to GET /eve/v1/session/:childSessionId/stream to follow child progress.
When the child needs shared procedures, duplicate skill markdown under each subagent’s skills/ or share typed helpers through lib/. There is no cross-agent skill visibility.
Reach for a declared subagent when the task needs a different specialist surface. Reach for a skill when the root agent keeps its identity and only needs an optional procedure.
Choosing a context lever
| Need | Use |
|---|---|
| Permanent identity and standing rules | instructions.md / instructions.ts |
| Build-time prompt composition from typed helpers | instructions.ts with defineInstructions |
| Optional procedures that should not bloat every turn | agent/skills/ + load_skill |
| Per-caller instructions or skill sets | defineDynamic in agent/instructions/ or agent/skills/ |
| Per-session or per-step tool sets | defineDynamic in agent/tools/ |
| File inspection or command execution | Sandbox tools against /workspace |
| Different prompt, tools, or sandbox for a subtask | Declared subagent under agent/subagents/<id>/ |
| Parallel work on the same files with same tools | Built-in agent tool |
| Long sessions exceeding context window | compaction.thresholdPercent in agent.ts |
Prompt assembly reference
createResolvedRuntimeTurnAgent calls composeRuntimeBasePrompt with the resolved agent’s instructions, skills, connections, and workspace spec. Section order:
- Instructions —
Instructions (<name>)\n<markdown> - Workspace — shallow root-entry overview when
rootEntriesis non-empty - Tool execution — parallel batch guidance when tools are available
- Connections — when connections are declared
- 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
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.mddocs/guides/auth-and-route-protection.mdpackages/eve/src/public/channels/index.tspackages/eve/src/public/connections/index.tspackages/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
| Capability | App runtime | Sandbox |
|---|---|---|
process.env / secrets | Yes | No |
Author Node.js code (defineTool, connections, state) | Yes | No |
| Network | Unrestricted (host policy) | Controlled by SandboxNetworkPolicy |
| Filesystem | App-owned paths | Isolated /workspace |
On Vercel, the app runtime is a Vercel Function; each sandbox is a Vercel Sandbox 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.
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.
| Backend | setNetworkPolicy support |
|---|---|
vercel() | Full policy: allow-lists, deny-all, header transforms |
microsandbox() | Local broker path with Vercel-style transforms |
docker() | Coarse "allow-all" / "deny-all" only |
just-bash() | Rejects setNetworkPolicy — no brokering surface |
See Sandbox 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:
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
401so re-authorization does not re-read a stale token
The model never receives connection tokens. Tool execute and MCP client code inject them into outbound requests on the trusted side.
Channel signature verification
Channels are the agent’s ingress surface. Platform channels verify inbound webhooks before deriving caller identity; custom channels should follow the same contract.
Built-in verifiers
| Channel | Mechanism | Replay protection |
|---|---|---|
| Slack | HMAC-SHA256 over v0:{timestamp}:{body} (X-Slack-Signature) | 5-minute timestamp skew |
| GitHub | HMAC-SHA256 over raw body (X-Hub-Signature-256) | — |
| Twilio | HMAC-SHA1 over URL + sorted form params (X-Twilio-Signature) | — |
| Telegram | Constant-time compare of X-Telegram-Bot-Api-Secret-Token | — |
All HMAC comparisons use timingSafeEqual — never === on signature strings.
Each channel also accepts an optional webhookVerifier callback for forwarded traffic (for example Connect-authenticated webhooks verified with Vercel OIDC instead of the platform signing secret).
Identity derivation rules
- Verify signatures in constant time over the raw request body (or delegate to a trusted verifier).
- Do not trust body-supplied identity. Derive
principalIdfrom fields parsed only after verification succeeds — for example Slack’steam_id+user_idfrom a signed payload, or Twilio’sFromfrom verified form params. AprincipalIdclaimed in an unauthenticated JSON body is attacker-controlled and enables cross-user impersonation.
Custom dashboard-style webhooks should authenticate the raw body with HMAC, compare in constant time, and only then map verified fields to SessionAuthContext.
Route auth (fail-closed defaults)
Route auth is inbound HTTP protection on the Eve channel. It runs at the channel layer before any model work starts.
Protected routes
eveChannel({ auth }) guards these routes via routeAuth:
| Route | Auth required |
|---|---|
POST /eve/v1/session | Yes |
POST /eve/v1/session/:sessionId | Yes |
GET /eve/v1/session/:sessionId/stream | Yes |
GET /eve/v1/health | No — always public for load balancers |
Custom defineChannel routes should call routeAuth(request, auth) to reuse the same walk semantics, or emit createUnauthorizedResponse(...) for hand-rolled checks.
Ordered auth walk
auth accepts a single AuthFn or an ordered array. Each entry has three outcomes:
| Return value | Effect |
|---|---|
SessionAuthContext | Accept request; halt walk |
null / undefined | Skip to next entry |
Throw UnauthenticatedError / ForbiddenError | Reject with structured 401 / 403 |
If every entry skips — including auth: [] — the request gets 401. Admitting anonymous callers requires an explicit final none() entry.
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";
export default eveChannel({
auth: [localDev(), vercelOidc()],
});
Shipped verifiers
| Helper | Use when |
|---|---|
localDev() | Loopback hostname or vercel dev (VERCEL=1 + VERCEL_ENV=development) |
vercelOidc() | Vercel deployment; verifies bearer JWT against Vercel OIDC issuer |
httpBasic() | Shared username/password for operators |
jwtHmac() / jwtEcdsa() | Custom JWT signers |
oidc() | Arbitrary OIDC issuer |
none() | Explicit anonymous access (must be final entry) |
placeholderAuth() | Scaffold guardrail — structured 401 in production until replaced |
eve init scaffolds [localDev(), vercelOidc(), placeholderAuth()]. In production, placeholderAuth() throws UnauthenticatedError with code eve_production_auth_not_configured so half-configured apps fail closed instead of serving open routes. Replace it with your app’s AuthFn before browser traffic arrives. Deleting agent/channels/eve.ts falls back to [localDev(), vercelOidc()], which still rejects production browser callers.
Route-auth secrets (ROUTE_AUTH_BASIC_PASSWORD, JWT signing keys) live in environment variables and are re-materialized at boot — never in compiled .eve/ artifacts.
createIpAllowList(...) and isIpAllowed(...) can drop requests before auth and runtime execution based on client IP.
Session auth propagation
After route auth succeeds, buildRunContext seeds AuthKey and InitiatorAuthKey into the runtime context:
| Field | Meaning |
|---|---|
ctx.session.auth.current | Caller on the active inbound turn |
ctx.session.auth.initiator | Caller that started the durable session |
Follow-up messages update auth.current but leave auth.initiator pinned. Both are null only on internal paths (subagents, schedules) that never traversed an authored route. Use these principals to scope tools, resolve dynamic capabilities, or enforce tenant boundaries — there is no second per-session ownership ACL beyond route auth.
Authored markdown is data
Skills, schedules, and instructions authored as markdown carry YAML frontmatter parsed strictly as data. Eve disables gray-matter’s built-in javascript / js frontmatter engines (which would eval() on parse). A ---js or ---javascript fence throws "JavaScript frontmatter is not supported." instead of executing. Frontmatter must parse to a plain YAML object.
Pre-production checklist
Related pages
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.mdxdocs/skills.mdxpackages/eve/src/public/definitions/instructions.tspackages/eve/src/public/skills/index.tspackages/eve/src/compiler/normalize-skill.tsapps/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
| Surface | Authored path | When it enters context | Use for |
|---|---|---|---|
| Instructions | agent/instructions.md, .ts, or instructions/ | Every turn, prepended to the system prompt | Permanent identity, tone, standing rules |
| Skills | agent/skills/* | On demand via load_skill tool result | Long or situational procedures |
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(),
});
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",
},
});
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
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.
Workspace seeding to /workspace/skills
Eve does not mount the full agent/ tree into the sandbox. Only two sources land in the workspace:
agent/skills/**→/workspace/skills/<name>/...agent/sandbox/workspace/**→/workspace/...
At compile time, materializeWorkspaceResources writes per-agent skill trees under .eve/compile/workspace-resources/<nodeId>/skills/. Flat markdown and module-backed skills normalize into a package directory with SKILL.md at the root; packaged skills copy their full directory tree. Sandbox templates prewarm with per-agent skill seed files.
| Authored shape | Sandbox path |
|---|---|
agent/skills/forecast.md | /workspace/skills/forecast/SKILL.md |
agent/skills/research/SKILL.md + siblings | /workspace/skills/research/SKILL.md, /workspace/skills/research/references/... |
defineSkill with files | /workspace/skills/<name>/<relativePath> for each entry |
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 viadefineInstructionsagent/skills/— returns the set of skills a caller can load viadefineSkill
Both read ctx.session.auth or channel metadata. Dynamic skills reuse the same Available skills formatter for durable context announcements.
Discovery diagnostics
Run eve info or eve build to surface discovery issues. Common skill and instruction failures:
| Code | Cause |
|---|---|
discover/required-instructions-missing | Root agent has no instructions.md, .ts, or instructions/ directory |
discover/slot-collision | Both instructions.md and instructions.ts at the agent root |
discover/skill-collision | Conflicting sources for the same skill id (e.g. foo.md and foo/ directory) |
discover/skill-markdown-missing | Packaged skill directory lacks SKILL.md |
discover/skill-frontmatter-invalid | Invalid YAML frontmatter or missing required description on packaged SKILL.md |
Recommended layout
Related pages
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.mdxpackages/eve/src/public/definitions/tool.tspackages/eve/src/public/tools/index.tspackages/eve/src/compiler/normalize-tool.tsapps/fixtures/weather-agent/agent/tools/get_weather.tsdocs/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
defineTool stamps a brand that lifecycle code validates; raw object literals are rejected at compile time.
The ctx parameter
execute receives a ToolContext — SessionContext plus token accessors when auth is declared.
| Member | Purpose |
|---|---|
ctx.session.id | Active session identifier |
ctx.session.auth | Caller snapshot from route auth (current, initiator) |
ctx.session.turn | Active turn metadata |
ctx.session.parent | Parent lineage when running inside a subagent |
ctx.getSandbox() | Resolves the live sandbox handle |
ctx.getSkill(id) | Reads a packaged skill’s metadata and files |
ctx.getToken() | Resolves the bearer for a declared auth strategy |
ctx.requireAuth() | Forces the authorization flow before proceeding |
getToken and requireAuth throw when the tool does not declare auth. These APIs are live only inside active authored runtime execution (tools, hooks, channel handlers). Calling them at module top level throws.
Running in the app runtime is what lets a tool import shared code from lib/, read secrets from the environment, and participate in Eve’s durable pause/resume model. See State, hooks, and session context 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);
},
});
| Helper | Behavior |
|---|---|
never() | Never require approval (equivalent to omitting needsApproval). |
once() | Require approval only the first time the tool runs in a session; auto-allow after an explicit approval. A denial leaves the tool unrecorded, so the next call prompts again. Keys off the bare tool name. |
always() | Require approval before every call. |
For input-aware decisions, pass a custom predicate instead of a helper. It receives { toolName, toolInput, approvedTools } and returns a boolean. toolInput can be undefined, so guard access:
needsApproval: ({ toolInput }) => (toolInput?.amount ?? 0) > 1000,
How approval pause and resume works
Approvals and ask_question share one protocol:
- The model requests input (approval or a question).
- Eve emits an
input.requestedstream event carrying the pending requests. - The turn parks at
session.waiting, durably. - The client answers with
inputResponses(structured, keyed byrequestId) or a normal follow-upmessage. 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}.` };
},
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/evefor 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 shape | File | Tool name(s) |
|---|---|---|
Single defineTool(...) | agent/tools/analytics.ts | analytics |
Map { export, query } | agent/tools/tenant.ts | tenant__export, tenant__query |
null | any | Contributes nothing for that event |
A map return always uses slug__key, even for a single entry, so adding a second tool later never renames the first.
Resolver events
| Event | Resolver runs | Tools available for |
|---|---|---|
session.started | Once per session | Every model call in the session |
turn.started | Once per turn | Every model call in the turn |
step.started | Before each model call | That model call only |
When an event fires, the channel adapter handler runs first, then stream-event hooks, then dynamic resolvers. The tool loop reads the current set right before each model call. Resolvers across files run concurrently. The most recently fired event for a file replaces that file’s earlier contribution.
Inline execute requirement
Write execute as an inline function expression placed directly as the property value. The bundler transform does not detect execute: myFn or execute: makeFn(), so those tools work on the first step but do not survive replay after a crash or resume. On later steps the transform reconstructs each execute from stored closure variables.
needsApproval on dynamic tools is only honored for step.started-scoped entries whose live execute closures survive into the harness. Session- and turn-scoped dynamic tools replay from durable metadata and cannot carry a function across replay.
Durability and idempotency
| Phase | Behavior |
|---|---|
Discovery / eve build | Compiles descriptors; never runs execute |
| Model sees descriptors | Tool names, descriptions, and input schemas only |
| Tool call executes | execute runs in the app runtime |
| Step completes | Result is recorded durably |
| Resume / replay | Completed steps return the recorded result; interrupted steps re-run execute |
Treat charges, emails, and other irreversible side effects as idempotent, or gate them behind needsApproval.
Verify tools are discovered
After authoring, confirm the tool appears in the agent manifest:
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
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.mdxpackages/eve/src/public/connections/index.tspackages/eve/src/compiler/normalize-connection.tspackages/eve/src/protocol/routes.tspackages/eve/src/runtime/session-callback-route.tsapps/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.
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
| Field | Purpose |
|---|---|
url | MCP server HTTP endpoint (Streamable HTTP or SSE). |
description | Model-facing summary of the connection and its tools. |
auth | Optional authorization strategy (see Authorization). |
headers | Optional extra HTTP headers; stacks with auth. |
tools | Optional filter: exactly one of allow or block on remote tool names. |
approval | Optional per-connection approval gate (never(), once(), always() from eve/tools/approval). |
Filtered tools never appear in connection__search results.
OpenAPI connections
defineOpenAPIConnection turns an OpenAPI 3.x or Swagger 2.0 document into one tool per operation.
import { defineOpenAPIConnection } from "eve/connections";
export default defineOpenAPIConnection({
spec: "https://api.tfl.gov.uk/swagger/docs/v1",
description: "Transport for London Unified API from its public Swagger 2.0 document.",
operations: { allow: ["Journey_Meta"] },
});
Each operation becomes connection__<connection>__<operationId>. When operationId is missing, Eve synthesizes <method>_<sanitized-path>.
OpenAPI-specific fields
| Field | Purpose |
|---|---|
spec | HTTPS URL fetched at runtime, or an inline parsed object. |
baseUrl | Optional override for the document’s first usable servers entry. |
operations | Filter keyed on operationId (allow or block); mirrors MCP tools. |
auth, headers, and approval behave the same as MCP connections.
Authorization
Eve sends resolved tokens as Authorization: Bearer <token>. Tokens are cached per workflow step keyed by (connectionName, principalKey) and never serialized into durable step payloads or conversation history.
Principal types
"app"— one shared credential across all sessions.getToken-only auth defaults here whenprincipalTypeis omitted."user"— per-user tokens keyed byissuer+id. Required for interactive OAuth (defineInteractiveAuthorizationpins"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):
getToken— return a cached token or throwConnectionAuthorizationRequiredErrorto start consent.startAuthorization— runs in a durable step; returns a user-facingchallengeand optional JSON-serializableresume(for example a PKCE verifier).completeAuthorization— exchanges the IdP callback for aTokenResult.
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
| Field | Purpose |
|---|---|
url | Authorize URL for redirect or device flows. |
userCode | Device code for device flows. |
instructions | Call to action when no URL is available. |
displayName | Provider label for channel sign-in UI (presentation only). |
displayName on the auth definition overrides a challenge-level value. Identity (scope, token cache, callback URL) stays keyed by the path-derived connection name.
Authorization errors
Throw these from getToken or completeAuthorization:
ConnectionAuthorizationRequiredError(connectionName)— user must authorize; emitsauthorization.requiredand, for interactive strategies, parks the turn.ConnectionAuthorizationFailedError(connectionName, { reason?, retryable? })— authorization failed.reasonis a stable machine-readable code onauthorization.completedevents.retryabledefaults totrue; setfalsefor terminal cases likeaccess_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.
:::
The handler:
- Parses query and form-encoded params into an
AuthorizationCallback(params only — request headers are dropped). - Calls
resumeHook(token, { kind: "deliver", payloads: [{ authorizationCallback }] })to wake the suspended turn. - Returns an “Authorization complete” landing page (or
404when 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 authoredgetToken. Cache miss on interactive strategies throwsConnectionAuthorizationRequiredErrorand parks the turn.ctx.requireAuth()— throwsConnectionAuthorizationRequiredErrorwithout resolving a token first; use to gate on sign-in or to re-challenge after a downstream401.
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
| Symptom | Likely cause |
|---|---|
connection__search missing | Agent has no files under agent/connections/. |
| Connection in prompt but search returns errors | Remote server unreachable, invalid url/spec, or auth failure during listTools. |
Connection callback not pending (404) | Callback arrived after the park expired, or token mismatch. |
| Infinite sign-in loop | Token rejected immediately after completeAuthorization; runtime fails with token_rejected_after_authorization. |
principal_required | principalType: "user" connection invoked without an authenticated user session. |
Run eve info to verify connection discovery. Check .eve/discovery/diagnostics.json after eve build for compile-time validation errors.
Related pages
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.mdxdocs/channels/custom.mdxpackages/eve/src/public/channels/index.tspackages/eve/src/compiler/normalize-channel.tspackages/eve/src/internal/nitro/routes/channel-dispatch.tspackages/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:
- Normalize platform input into a user message (string or
UserContent). - Own the continuation token — the channel-local resume handle for a conversation on that surface. The runtime namespaces tokens as
<channelName>:<rawToken>(for exampleslack:C0123ABC:1800000000.001234). - 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:
| Helper | Purpose |
|---|---|
send(message, options) | Start or resume a session on this channel |
getSession(sessionId) | Look up an existing session (for streaming) |
receive(targetChannel, input) | Hand inbound work to another channel |
params | Path parameters from [name] segments |
waitUntil(promise) | Extend request lifetime for post-ack background work |
requestIp | Client IP, or null when unavailable |
WebSocket routes use WS(path, handler). The handler runs once per upgrade and returns lifecycle hooks (upgrade, open, message, close, error). For third-party SDKs that bind directly to a Node http.Server, createWebSocketUpgradeServer() provides a compatibility bridge.
ChannelDefinition fields
Continuation tokens
Pass the channel-local raw token to send(). The framework prepends the channel name before handing it to the runtime. Platform factories ship helpers such as slackContinuationToken(channelId, threadTs) and twilioContinuationToken(from, to). Custom channels define their own join format.
Re-key a parked session with channel.setContinuationToken(rawToken) or session.setContinuationToken(rawToken) from a context() factory. Inbound deliveries addressed to the old token are dropped after the next step boundary.
Default Eve HTTP channel
The Eve channel is the framework’s default HTTP session API — the routes the terminal UI, eve/client, useEveAgent, and curl use. It is enabled by default without an authored file. Add agent/channels/eve.ts only to override behavior (most often route auth).
eveChannel() from eve/channels/eve wraps defineChannel and mounts:
| Method | Path | Behavior |
|---|---|---|
POST | /eve/v1/session | Create session; returns 202 with sessionId and continuationToken |
POST | /eve/v1/session/:sessionId | Continue session with message and/or inputResponses |
GET | /eve/v1/session/:sessionId/stream | NDJSON event stream (startIndex query param supported) |
When no authored eve.ts exists, the framework injects eveChannel({ auth: [localDev(), vercelOidc()] }) — the same defaults the web scaffold writes.
import { eveChannel } from "eve/channels/eve";
import { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";
export default eveChannel({
auth: [localDev(), vercelOidc(), placeholderAuth()],
});
auth is required. Pass a single AuthFn or an ordered array walked by routeAuth: the first entry returning a SessionAuthContext wins; null/undefined skips to the next; exhaustion returns 401. Include none() last for anonymous traffic.
Platform channel factories
Built-in channels are defineChannel instances with platform-specific verification, parsing, and delivery. Default-export the factory result from agent/channels/<name>.ts.
| Factory | Import path | Default route | Verification |
|---|---|---|---|
eveChannel | eve/channels/eve | /eve/v1/session, /eve/v1/session/:sessionId, /eve/v1/session/:sessionId/stream | routeAuth (JWT, OIDC, Basic, etc.) |
slackChannel | eve/channels/slack | POST /eve/v1/slack | Slack v0 HMAC (X-Slack-Signature) or custom webhookVerifier |
discordChannel | eve/channels/discord | POST /eve/v1/discord | Ed25519 (X-Signature-Ed25519) or webhookVerifier |
teamsChannel | eve/channels/teams | POST /eve/v1/teams | Bot Framework JWT or webhookVerifier |
telegramChannel | eve/channels/telegram | POST /eve/v1/telegram | X-Telegram-Bot-Api-Secret-Token or webhookVerifier |
twilioChannel | eve/channels/twilio | POST /eve/v1/twilio/messages, /voice, /voice/transcription | X-Twilio-Signature over URL + sorted form params |
githubChannel | eve/channels/github | POST /eve/v1/github | X-Hub-Signature-256 HMAC or webhookVerifier |
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.
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:
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).
eve channels list
eve channels list --json
When no channels exist, the command suggests running eve channels add.
Choosing a channel
| Goal | Channel |
|---|---|
Browser chat, SDK clients, curl | Eve channel (default) + client hooks |
| Slack mentions, DMs, buttons | slackChannel |
| Discord slash commands, components | discordChannel |
| Microsoft Teams messages, Adaptive Cards | teamsChannel |
| Telegram bot messages | telegramChannel |
| SMS or speech-transcribed calls | twilioChannel |
| GitHub @mentions, PR review | githubChannel |
| Internal webhook, WebSocket, custom transport | defineChannel |
Troubleshooting
| Symptom | Likely cause |
|---|---|
401 on platform webhook | Missing or wrong signing secret; clock skew beyond 5 minutes; custom webhookVerifier returned falsy |
401 on /eve/v1/session | routeAuth exhausted without a winner; replace placeholderAuth() in production |
Route not found (404) | Route path or method mismatch; run eve info or eve build and check compiled channel entries |
disableRoute() build error | File stem does not match a framework channel name |
| Twilio signature mismatch | webhookUrl does not match the URL Twilio configured |
| Session not resuming | Continuation token format changed; old-token deliveries dropped after re-key |
Related pages
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.mdxpackages/eve/src/public/definitions/sandbox.tspackages/eve/src/compiler/normalize-sandbox.tspackages/eve/src/execution/sandbox/prewarm.tspackages/eve/src/compiler/workspace-resources.tsdocs/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
Default tools and proxy execution
The model receives shell and file access through built-in tools. Each tool handler stays in the harness; execution delegates to sandbox primitives:
| Tool | Sandbox executor | Working directory |
|---|---|---|
bash | executeBashOnSandbox | /workspace |
read_file | executeReadFileOnSandbox | /workspace |
write_file | executeWriteFileOnSandbox | /workspace |
glob | executeGlobOnSandbox | /workspace |
grep | executeGrepOnSandbox | /workspace |
requireSandboxSession() centralizes context lookup: it reads SandboxKey from the active async-local scope, calls SandboxAccess.get(), and throws when sandbox access is missing or unavailable. Framework tools import this preamble statically so Nitro bundles can trace them; backend bindings load lazily inside the execution layer.
write_file enforces a read-before-write guarantee: the model must call read_file on a path before overwriting it. glob and grep probe for rg once per session and fall back to POSIX find/grep when ripgrep is absent. bash tail-truncates stdout and stderr to shared output limits.
Author a shell tool with defineBashTool to reuse the same executor core:
import { defineBashTool } from "eve/tools";
export default defineBashTool({
description: "Run a one-off shell check in the workspace.",
});
SandboxSession API
ctx.getSandbox() is async, takes no arguments, and works only inside authored runtime functions (tools, hooks, channel handlers). Relative paths resolve from /workspace; absolute paths pass through unchanged.
| Method | Behavior |
|---|---|
run({ command }) | Run one command, block until exit, return { stdout, stderr, exitCode } |
spawn(options) | Launch a long-running process; returns SandboxProcess with stdout/stderr streams, wait(), kill() |
readTextFile / writeTextFile | UTF-8 (or specified encoding) text I/O; readTextFile supports 1-based line ranges |
readBinaryFile / writeBinaryFile | Raw byte I/O |
readFile / writeFile | Stream byte I/O |
removePath({ path, force, recursive }) | Delete files or directories |
resolvePath(path) | Anchor a relative path to /workspace/... |
setNetworkPolicy(policy) | Change egress mid-turn (backend-dependent) |
sandbox.id is a stable per-session identifier across reconnects. Use it as a cache key for per-session state that must outlive individual step executions.
Option types (SandboxSpawnOptions, SandboxReadTextFileOptions, and others) export from eve/sandbox.
Workspace seeding
Mount authored files into /workspace at session start by placing them under agent/sandbox/workspace/. This requires the folder layout (agent/sandbox/sandbox.ts), not the top-level shorthand:
:::files agent/sandbox/ sandbox.ts ← optional override workspace/ schema.sql ← lands at /workspace/schema.sql scripts/run.sh ← lands at /workspace/scripts/run.sh :::
At compile time, Eve copies sandboxWorkspaces and skill packages into .eve/compile/workspace-resources/<nodeId>/, hashes the tree, and records a workspaceResourceRoot on the compiled manifest. Prewarm and session create pass those files as seedFiles to the backend.
Every file under workspace/ mirrors into the sandbox cwd with structure intact. Eve lists top-level entries in the model prompt automatically.
Authoring with defineSandbox
defineSandbox from eve/sandbox is an identity helper that attaches types. backend is optional; when omitted, the runtime substitutes defaultBackend().
Two discovery layouts exist:
| Layout | Path | Use when |
|---|---|---|
| Shorthand | agent/sandbox.ts | Definition only, no seeded files |
| Folder | agent/sandbox/sandbox.ts + workspace/ | Seeded files or folder organization |
If both exist, the folder layout wins. Subagents override independently at subagents/<name>/sandbox.ts (or the folder form) and do not inherit the parent’s sandbox.
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" });
},
});
When no agent/sandbox.ts (or folder equivalent) exists, the framework auto-provides a default sandbox via defaultBackend().
Backends
A SandboxBackend implements prewarm (build-time template capture) and create (runtime session open/reattach). Eve ships four pinned factories plus an availability-aware default:
| Import | Backend name | Runs where |
|---|---|---|
vercel() from eve/sandbox/vercel | vercel | Vercel Sandbox |
docker() from eve/sandbox/docker | local (Docker) | Local Docker daemon via CLI |
microsandbox() from eve/sandbox/microsandbox | local (microsandbox) | Lightweight local VM |
justbash() from eve/sandbox/just-bash | local (just-bash) | Pure-JS interpreter, no real binaries |
defaultBackend() from eve/sandbox | Resolved at runtime | Availability chain |
Local vs Vercel
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
| Hook | Scope | When it runs | What persists |
|---|---|---|---|
bootstrap({ use }) | Template | Once when the template is built (prewarm) | Template filesystem and supported backend metadata |
onSession({ use, ctx }) | Durable session | Once per session, inside active runtime context | Per-session config (network policy, resources, credentials) |
Put reusable setup in bootstrap (clone baseline repo, install dependencies). Put per-session setup in onSession (network lockdown, per-user markers). Because onSession runs inside the harness, it can read ctx.session.auth without baking credentials into the template.
Network policy set on the backend factory applies before authored bootstrap. Policy set in onSession’s use() overrides per session. Call sandbox.setNetworkPolicy(...) on the live handle to change policy mid-turn.
Template key strategies
createRuntimeSandboxTemplatePlan chooses how templates are keyed:
| Plan kind | Condition | Prewarm |
|---|---|---|
none | No bootstrap, no workspace seeds | Skipped (templateKey is null) |
workspace-content | Seeds only | Keyed by workspace content hash |
bootstrap | bootstrap() present | Keyed by revalidationKey, source hash, and content hash |
Template keys include nodeId so root and subagent sandboxes do not collide. Vercel stable templates key on VERCEL_PROJECT_ID so build-time prewarm and deployed runtime derive the same key.
Build-time prewarm
Prewarm captures reusable sandbox templates before traffic arrives. prewarmSandboxes iterates every registered sandbox in the compiled agent graph and calls backend.prewarm(...) with bootstrap, seedFiles, and a stable templateKey.
When prewarm runs
| Environment | Trigger | Failure behavior |
|---|---|---|
| Vercel build | VERCEL and VERCEL_DEPLOYMENT_ID both set | Build fails (runVercelBuildPrewarm) |
eve dev | Background prewarm on startup and after authored-source changes | Logged error; retried on first sandbox access |
eve start (production) | prewarmBuiltAppSandboxes before serving | Startup failure |
Prewarm covers template construction only. onSession() still runs at runtime, once per durable session.
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
| Symptom | Likely cause |
|---|---|
SandboxTemplateNotProvisionedError | Template not prewarmed; dev retries automatically, production needs successful build prewarm |
agent/sandbox/workspace/skills/ discovery error | Reserved path; move skills to agent/skills/ |
This tool requires sandbox access on the runtime context | Tool called outside a managed harness step |
filePath must be an absolute path | Model passed a relative path to read_file/write_file; use /workspace/... |
| Build fails during sandbox prewarm | bootstrap() threw or backend credentials missing; fix bootstrap or Vercel Sandbox access |
| just-bash / microsandbox missing in production | Optional peers not installed; eve dev auto-installs, production requires explicit install |
Related pages
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.mdxdocs/schedules.mdxpackages/eve/src/compiler/normalize-subagent.tspackages/eve/src/compiler/normalize-schedule.tspackages/eve/src/execution/subagent-adapter.tspackages/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
| Surface | Location | Runtime behavior |
|---|---|---|
Built-in agent tool | Shipped with every agent (override at agent/tools/agent.ts) | Copies the current agent; shares parent sandbox and tools |
| Local subagent | agent/subagents/<id>/agent.ts with defineAgent | Isolated child agent; own instructions, tools, skills, sandbox |
| Remote subagent | agent/subagents/<id>/agent.ts with defineRemoteAgent | Async POST to another Eve deployment; parent parks until callback |
| Schedule | agent/schedules/<name>.{ts,md} | Cron-fired session; markdown (task mode) or handler (run) |
All subagent kinds lower to the same tool input schema:
{
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
defineStatedata. - File writes from parallel
agentcalls 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).
:::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.
Isolation boundary
A declared subagent inherits nothing from the root’s authored slots. Discovery treats agent/subagents/<id>/ as its own agent root; absent slots fall back to framework defaults, not the parent’s versions.
| Slot | Built-in agent tool | Declared subagent |
|---|---|---|
| Instructions | Inherited (copy) | Own instructions.{md,ts}, optional |
| Tools | Inherited | Own tools/ |
| Connections | Inherited | Own connections/ |
| Skills | Inherited | Own skills/ |
| Sandbox | Shared with parent | Own sandbox/, else framework default |
| Hooks | Inherited | Own hooks/ |
| State | Fresh | Fresh |
| Channels | Root-only | Root-only |
| Schedules | Root-only | Root-only |
channels/ and schedules/ are not supported inside local subagents. Duplicate shared procedures by copying skill markdown into each child’s skills/, or share typed helpers via lib/.
Remote subagents
Use defineRemoteAgent when the specialist is a separately deployed Eve agent, not a directory in your repo. The file still lives under agent/subagents/<id>/, but only agent.ts is allowed — the compiler rejects local package entries (tools/, skills/, connections/, etc.) on remote definitions.
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(),
});
Remote dispatch is asynchronous:
- Parent starts a task-mode session on the remote’s
POST /eve/v1/session, passing a framework callback URL. - Parent turn parks until the remote posts a terminal callback.
- 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;
}
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.
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 arg | Purpose |
|---|---|
receive(channel, { message, target, auth }) | Start a session on another channel (same contract as route handlers) |
waitUntil(promise) | Extend the cron task lifetime past handler return so parked sessions settle |
appAuth | Pre-built app principal ({ authenticator: "app", principalId: "eve:app", principalType: "runtime" }) |
Wrap receive calls in waitUntil so Nitro awaits background work before the task ends.
Dev schedule dispatch
eve dev never fires schedules on their cron cadence. Trigger one manually through the dev-only dispatch route, which runs the same path as production (dispatchScheduleTask → ScheduleDispatcher.trigger):
:::endpoint POST /eve/v1/dev/schedules/:scheduleId Dev-only one-shot schedule dispatch. Re-resolves schedules from disk on every call so authored-source watcher edits apply without restart. No auth (local dev server only). Production builds never mount this route. :::
: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.
When to use what
| Need | Use |
|---|---|
| Same agent, parallel file work | Built-in agent tool |
| Different prompt, tools, or sandbox | Local subagent under subagents/<id>/ |
| Specialist owned by another deployment | defineRemoteAgent |
| Optional procedure, same identity | Skill (load_skill) — lighter than a subagent |
| Periodic agent work | Schedule under agent/schedules/ |
| Schedule that hands off to Slack/Discord/etc. | Schedule handler with receive + waitUntil |
Related pages
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.mdpackages/eve/src/public/definitions/agent.tspackages/eve/src/compiler/normalize-agent-config.tspackages/eve/src/compiler/model-catalog.tsapps/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.
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",
},
},
},
});
Routing at compile time
Eve classifies how the model reaches inference and embeds routing in the compiled manifest:
| Authored value | Routing | Runtime behavior |
|---|---|---|
Gateway id string (anthropic/claude-sonnet-4.6) | gateway | Routed through the Vercel AI Gateway |
gateway(...) instance | gateway | Same as a string id |
Direct provider instance (anthropic(...)) | external | Bypasses the gateway; talks to the provider endpoint |
Gateway id + providerOptions.gateway.byok | gateway with byok | Forwards a bring-your-own-key provider block through the gateway |
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,
},
});
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 },
});
| Resolution | Effective value |
|---|---|
experimental.codeMode: true | Code mode on |
experimental.codeMode: false | Code mode off (overrides env) |
| Flag omitted | Falls back to EVE_EXPERIMENTAL_CODE_MODE env var; only "1" enables it |
When any node in the compiled graph enables code mode (or the Workflow tool), the hosted build bundles sandbox worker assets. Code mode requires a configured sandbox backend.
Output schema
outputSchema declares a structured return type for task-mode runs: subagent delegation, schedule invocations, and remote jobs. Eve normalizes Standard Schema or JSON Schema objects to JSON Schema at compile time.
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"],
},
});
During compilation, Eve merges externalDependencies from the root agent down through the subagent graph and passes the merged list to every authored module compile step. The framework also auto-traces @napi-rs/keyring (pulled in transitively through Vercel OIDC auth) without requiring author configuration.
Description (subagents)
description is optional on the root agent. Local subagents under agent/subagents/<id>/agent.ts must include description — Eve surfaces it as the lowered subagent tool’s description so the parent can decide when to delegate. Build fails with a clear error when a subagent omits it.
Field reference
| Field | Type | Default | Scope |
|---|---|---|---|
model | string | LanguageModel | anthropic/claude-sonnet-4.6 when agent.ts omitted | Runtime turns |
modelContextWindowTokens | number | catalog lookup | Compaction threshold math |
modelOptions | { providerOptions? } | none | Model call options |
compaction | object | harness defaults (thresholdPercent → 0.9) | Context management |
experimental.codeMode | boolean | env backstop, else false | Tool exposure mode |
outputSchema | Standard Schema or JSON Schema | none | Task-mode structured output |
build.externalDependencies | string[] | none | Hosted build packaging |
description | string | none | Subagent tool description (required for local subagents) |
defineAgent rejects unknown keys at normalization time. TypeScript checks the argument against AgentDefinition via ExactDefinition, so typos surface as compile errors in the editor.
Adjacent configuration
| Concern | Location |
|---|---|
| System prompt | agent/instructions.md or agent/instructions.ts |
| Per-tool HITL approval | agent/tools/*.ts |
| Inbound auth and network policy | channel definitions (agent/channels/) |
| Sandbox and workspace | agent/sandbox/ |
| Telemetry | agent/instrumentation.ts |
| Eval suites | evals/ |
Troubleshooting
| Symptom | Likely cause |
|---|---|
The "model" field is required. | agent.ts exists but omits model |
does not have known AI Gateway context window metadata | Unlisted gateway model id without modelContextWindowTokens override |
to provide a valid AI SDK language model | Provider instance missing provider, modelId, doGenerate, or doStream; or unsupported specificationVersion |
missing a "description" field | Local subagent agent.ts without description |
Expected ... to match the public Eve shape | Unknown key, wrong type, or thresholdPercent outside 0–1 |
Run eve info for discovery diagnostics and eve build to surface compile-time validation errors before deployment.
Related pages
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.mddocs/guides/deployment.mdpackages/eve/src/cli/commands/link.tspackages/eve/src/cli/commands/deploy.tspackages/eve/src/internal/nitro/host/configure-nitro-routes.tspackages/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).
| Route | Auth |
|---|---|
POST /eve/v1/session | routeAuth walk |
POST /eve/v1/session/:sessionId | routeAuth walk |
GET /eve/v1/session/:sessionId/stream | routeAuth walk |
GET /eve/v1/health | Always public (no walk) |
GET /eve/v1/info uses the framework default chain [localDev(), vercelOidc()] independently of the authored channel file.
Ordered auth walk
auth accepts a single AuthFn or an ordered array. routeAuth walks entries until one accepts:
| Outcome | Behavior |
|---|---|
Returns SessionAuthContext | Accept request; stop walk |
Returns null / undefined | Skip to next entry |
Throws UnauthenticatedError / ForbiddenError | Reject with structured 401 / 403 |
Every entry skips (including auth: []) | Reject with 401 |
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
| Helper | Use when |
|---|---|
localDev() | Local development on loopback hostnames (localhost, *.localhost, 127.0.0.0/8, ::1); also accepts vercel dev (VERCEL=1 + VERCEL_ENV=development) |
vercelOidc() | Vercel deployments; verifies bearer JWTs from Vercel OIDC |
none() | Explicit anonymous access as the final entry |
httpBasic(...) | Shared username/password for operators or services |
jwtHmac(...) / jwtEcdsa(...) | Shared-secret or asymmetric JWT verification |
oidc(...) | Arbitrary OIDC issuer |
placeholderAuth() | Scaffold guardrail — see below |
vercelOidc() acceptance rules
- Tokens whose
project_idmatchesVERCEL_PROJECT_IDare always accepted (internal runtime and subagent callers need no enumeration). - Tokens with
external_subauthenticate asprincipalType: "user"whenproject_idmatchesVERCEL_PROJECT_IDand environment matchesVERCEL_TARGET_ENV/VERCEL_ENV. Profile claims (name,picture,email) surface inctx.session.auth.current.attributes. - Tokens from other Vercel projects require an explicit
subjectslist (AWS IAM-style*wildcards). Build patterns withvercelSubject({ teamSlug, projectName, environment? })—environmentdefaults 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:
| Field | Meaning |
|---|---|
auth.current | Caller on the active inbound turn |
auth.initiator | Caller that started the durable session (pinned across follow-ups) |
Follow-up messages update auth.current but leave auth.initiator unchanged. Both are null only on internal paths (subagents) that never went through an authored route.
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
Route auth secrets
Reference from agent/channels/eve.ts auth helpers — for example ROUTE_AUTH_BASIC_PASSWORD for httpBasic(...), or JWT/OIDC signing keys for jwtHmac / jwtEcdsa / oidc.
Vercel deployment context
| Variable | Role |
|---|---|
VERCEL | Set during hosted builds and Vercel runtime; triggers .vercel/output emission and Vercel sandbox backend selection |
VERCEL_ENV | production, preview, or development; used by placeholderAuth() and OIDC user-token environment matching |
VERCEL_TARGET_ENV | Deployment target environment for OIDC user-token matching |
VERCEL_PROJECT_ID | Current project binding for vercelOidc() and stable sandbox template scoping |
VERCEL_DEPLOYMENT_ID | Enables build-time sandbox prewarm; keys source-graph templates |
VERCEL_AUTOMATION_BYPASS_SECRET | Local bypass for Vercel preview protection when driving remote deployments with eve dev <url> |
eve link and eve deploy
eve link
Interactive only — team and project pickers are the point of the command.
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 state | TTY | Behavior |
|---|---|---|
| Already linked | Any | Deploy immediately |
| Unlinked | Interactive | Walk eve link pickers, then deploy |
| Unlinked | Non-interactive | Exit with guidance to run eve link first |
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:
| Priority | Backend | Condition |
|---|---|---|
| 1 | vercel() | process.env.VERCEL is set |
| 2 | docker() | Docker daemon reachable |
| 3 | microsandbox() | Platform supported (Apple Silicon macOS or glibc Linux with KVM) |
| 4 | justbash() | Dependency-free fallback |
Selection is cached for the process lifetime. Pin a backend unconditionally with vercel(), docker(), microsandbox(), or justbash() when you need deterministic behavior.
Build-time sandbox prewarm
During hosted Vercel builds, Eve prewarms reusable Vercel Sandbox templates so the first session avoids cold-start cost.
When prewarm runs
Prewarm executes only when both VERCEL and VERCEL_DEPLOYMENT_ID are set (shouldPrewarmVercelBuild()). Dev runs and one-off local builds skip platform template provisioning.
When a sandbox is skipped
createRuntimeSandboxTemplateKey returns null (no prewarm target) when the template plan is kind: "none":
- No
bootstrap()hook and - No workspace seed files under
agent/sandbox/workspace/
Sandboxes with only onSession() and no seeds do not get a prewarm template.
Template keying
| Template kind | Keyed by |
|---|---|
workspace-content (seed files only) | Skills and workspace file contents (contentHash) |
bootstrap | Optional revalidationKey(), authored sandbox source hash, and seed contents |
source-graph | Full source graph hash |
Each graph node (nodeId) produces a distinct template key so root and subagent sandboxes do not collide.
Build logs report each template as reused cached or built. Summary line: initialized N sandbox templates (X reused, Y built).
Production verification
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 buildsucceeds; writes.vercel/outputwhenVERCELis set - Model credential and route-auth secrets set in Vercel env vars
-
Sandbox backend matches environment (
vercel()ordefaultBackend()) - Build-time prewarm reused or built templates without failing
-
placeholderAuth()replaced with real policy -
eve deploysucceeds - Health, session, and stream routes respond on the deployment URL
Related pages
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.mddocs/guides/hooks.mddocs/guides/session-context.mdpackages/eve/src/public/context/index.tspackages/eve/src/public/definitions/hook.tspackages/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 }));
defineState returns a StateHandle<T>:
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.
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
| Concern | defineState | External store / connection |
|---|---|---|
| Scope | One session | Cross-session, cross-user |
| Lifetime | Dies with the session | Independent of conversation |
| Use case | Running counters, in-conversation plans, glossaries | Durable records, shared data, queryable tables |
Stream hooks with defineHook
Hooks subscribe to the runtime NDJSON event stream from agent/hooks/. They run observe-only side effects after each event is durably recorded.
import { defineHook } from "eve/hooks";
export default defineHook({
events: {
async "session.started"(_event, ctx) {
console.info("session started", { sessionId: ctx.session.id });
},
async "message.completed"(event) {
console.info("model finished", { length: event.data.message?.length ?? 0 });
},
},
});
Import defineHook, HookDefinition, HookContext, and related types from eve/hooks.
Path-derived slug
The hook slug is the path-relative basename under agent/hooks/:
| File | Slug |
|---|---|
agent/hooks/audit.ts | audit |
agent/hooks/auth/load-profile.ts | auth/load-profile |
Discovery preserves lexicographic slug ordering when building the runtime registry.
Event subscribers
The events map keys handlers by stream event type. Use * to match every event.
| Event type | Typical use |
|---|---|
session.started | Session bootstrap, audit |
turn.started / turn.completed | Per-turn resets, metrics |
message.completed | Model output logging |
action.result | Tool result inspection |
step.started / step.completed / step.failed | Step-level tracing |
turn.failed / session.failed / session.completed | Failure and completion handling |
session.waiting | Parked-session notifications |
Handlers are observe-only. They cannot inject model context. To contribute runtime model messages, use defineDynamic and defineInstructions under agent/instructions/.
HookContext
Every handler receives (event, ctx) where ctx is the last argument. HookContext extends SessionContext with agent and channel metadata:
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
- Emit — the channel adapter handler runs, then the event is written to the durable stream.
- Hooks — typed handlers for the event type run first, then
*wildcard handlers. Return values are ignored. - 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:
| Field | Type | Description |
|---|---|---|
id | string | Active session identifier |
turn.id | string | Current turn identifier |
turn.sequence | number | Turn sequence within the session |
auth.current | SessionAuthContext | null | Caller for the active inbound turn |
auth.initiator | SessionAuthContext | null | Caller that started the durable session |
parent | SessionParent | undefined | Present for child subagent sessions |
SessionParent includes callId, sessionId, rootSessionId, and turn (the immediate parent’s turn metadata).
Behavior notes:
- Unprotected agents expose both
auth.currentandauth.initiatorasnull. - Top-level schedule sessions expose the framework app principal (
principalId: "eve:app",principalType: "runtime"). parentis 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
nameandfile(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());
},
});
The authorization machinery mirrors connection flows: per-step token caching, park/resume on interactive OAuth, loop-guard on tokens rejected immediately after sign-in, and token eviction on downstream 401s.
Where managed-context APIs are valid
All defineState operations, ctx accessors, and ALS-backed context reads require an active harness step (contextStorage.run).
| Surface | defineState / ctx | Notes |
|---|---|---|
defineTool(...).execute(input, ctx) | Valid | ToolContext when auth is declared |
defineHook event handlers | Valid | HookContext |
| Channel adapter event handlers | Valid | SessionContext |
defineDynamic resolver callbacks | Valid | Framework builds SessionContext internally |
Sandbox onSession hook | Valid | Receives { ctx, use }; runs inside ALS |
| Async boundaries within the same authored callback chain | Valid | ALS propagates across await |
| Module top-level evaluation | Throws | No active context at import time |
| Build scripts / discovery-time code | Throws | Outside runtime execution |
Schedule run handler | No ctx | Receives ScheduleHandlerArgs (receive, waitUntil, appAuth) |
Sandbox bootstrap hook | No ctx | Receives { use } only; runs at build/prewarm |
Instrumentation setup | No ctx | Domain-specific arguments |
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
| Need | Use |
|---|---|
| Observe runtime events (audit, metrics, alerting) | defineHook events.<type> |
| Provide structured input to the model on demand | defineTool |
| Change model context based on stream events | defineDynamic under agent/instructions/ |
| Subscribe to platform-specific ingress | Channel adapter handler |
| Durable per-session working memory | defineState |
Stream-event hooks and channel adapter event handlers are structurally similar. Choose the adapter handler for platform-specific behavior; choose events.* for agent-level behavior that fires across every channel.
Related pages
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.mddocs/evals/overview.mdxpackages/eve/src/public/definitions/instrumentation.tspackages/eve/src/evals/define-eval.tspackages/eve/src/evals/cli/eval.tspackages/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:
| Surface | Configured in instrumentation.ts? | What it records |
|---|---|---|
Workflow run tags ($eve.*) | No — automatic | Framework-owned attributes on each Vercel Workflow run. Dashboards stitch session, turn, and subagent runs into a tree and surface model and token usage. |
| OpenTelemetry export | Yes — setup, recordInputs, recordOutputs, functionId | Where AI SDK spans are exported and what they record. |
| Runtime context events | Yes — events["step.started"] | Per-model-call values merged into AI SDK telemetry spans. |
Workflow run tags are queryable in the Vercel Workflow dashboard (and the Agent Runs observability tab on Vercel deployments). OpenTelemetry and runtime context both ride on AI SDK spans and export to any OTel-compatible backend you configure.
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.
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
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.
Workflow run tags
Separately from OpenTelemetry, Eve tags every workflow run with reserved $eve.* attributes. Authored code cannot set or override the $eve. namespace. Tags are emitted automatically on every session, turn, and subagent run, whether or not instrumentation.ts is present. Tag writes are best-effort: a failure is logged once per process and swallowed so a broken tag emit never breaks the agent.
Structural tags
| Tag | Values / meaning |
|---|---|
$eve.type | "session", "turn", or "subagent" |
$eve.parent | Session id of the immediate parent |
$eve.root | Session id of the root session (group a tree with $eve.root=<id>) |
$eve.parent_call | Parent runtime-action tool call id (subagent rows only) |
$eve.parent_turn | Parent turn id that dispatched the subagent (subagent rows only) |
$eve.subagent | Compiled graph node id (subagent runs only) |
$eve.trigger | Channel kind that started the run |
$eve.title | Truncated title from the first user message |
Per-turn usage tags
Written on each step of a turn, accumulating cumulative totals (last write wins):
| Tag | Meaning |
|---|---|
$eve.model | Model id for the turn |
$eve.input_tokens | Running input token count |
$eve.output_tokens | Running output token count |
$eve.cache_read_tokens | Running cache-read token count |
$eve.cache_write_tokens | Running cache-write token count |
$eve.tool_count | Number of tools available to the turn |
On Vercel, the platform auto-detects eve and surfaces an Agent Runs view under Observability. That view is separate from OTel export — use OTel when you want spans in a third-party backend.
Eval authoring
Evals live under the app-root evals/ directory. Eve discovers *.eval.ts files recursively and requires exactly one evals/evals.config.ts at the root of evals/.
:::files my-agent/ ├── agent/ ├── evals/ │ ├── evals.config.ts # required run-wide defaults │ ├── smoke.eval.ts │ └── weather/ │ ├── brooklyn-forecast.eval.ts │ └── no-tools-for-greetings.eval.ts └── package.json :::
Eval identity is path-derived — do not author id or name. evals/weather/brooklyn-forecast.eval.ts becomes id weather/brooklyn-forecast. Directories group related evals; shared helpers go in sibling non-eval files (any name that does not end in .eval.ts).
defineEval
Each eval file default-exports one defineEval({ ... }) call (or an array of them for dataset fan-out). The only required field is test:
import { defineEval } from "eve/evals";
import { includes } from "eve/evals/expect";
export default defineEval({
description: "Basic message and tool-usage coverage for the weather agent.",
async test(t) {
await t.send("What is the weather in Brooklyn?");
t.completed();
t.calledTool("get_weather");
t.check(t.reply, includes("Sunny"));
},
});
Optional per-eval fields:
| Field | Purpose |
|---|---|
description | Human-readable summary (shown in --list output) |
judge | Override judge model for t.judge.* on this eval |
tags | Filter with eve eval --tag |
metadata | Passed through to reporters |
timeoutMs | Per-eval timeout (overridden by CLI --timeout) |
reporters | Reporters scoped to this eval only |
defineEval rejects legacy keys (input, run, checks, scores, expected, thresholds, parseOutput, model, requires, cases). Drive the agent inside test and assert inline.
defineEvalConfig
import { defineEvalConfig } from "eve/evals";
import { Braintrust } from "eve/evals/reporters";
export default defineEvalConfig({
judge: { model: "openai/gpt-5.4-mini" },
reporters: [Braintrust({ projectName: "my-agent" })],
maxConcurrency: 4,
timeoutMs: 120_000,
});
All fields are optional — an empty defineEvalConfig({}) satisfies the requirement. When set:
| Field | Default | Precedence |
|---|---|---|
judge.model | none | Per-call model → per-eval judge → config judge |
reporters | none | Suppressed by --skip-report |
maxConcurrency | 8 | CLI --max-concurrency overrides |
timeoutMs | none | Per-eval timeoutMs → CLI --timeout |
Array-exported eval files derive ids as <file-id>/<zero-padded-index> (e.g. weather/0000, weather/0001).
The t context
t is both the session driver and the assertion surface passed to test(t). There are no separate input, run, checks, or scores fields.
Drive the agent:
| Method | Purpose |
|---|---|
t.send(input) | Send one turn; resolves when the turn settles |
t.respond(...) / t.respondAll(optionId) | Resolve HITL input requests and resume |
t.sendFile(text, path, mediaType?) | Send text with a local file attached |
t.expectInputRequests(filter?) | Assert the run parked on HITL input |
t.newSession() | Start an additional independent session |
t.reply | Last assistant message on the primary session |
t.events / t.sessionId / t.state | Stream events, session id, resumable state |
t.log(message) | Structured log (visible with --verbose) |
Evals exercise the same HTTP session protocol as production clients. The runner boots a local dev server or targets a remote deployment via --url.
Assertions
Assertions record results during test(t) and are evaluated when the run completes. Every assertion returns a chainable handle; severity rides on the assertion itself — there is no separate thresholds map.
Three assertion surfaces
| Surface | Examples | Default severity |
|---|---|---|
| Run-level methods | t.completed(), t.calledTool("get_weather"), t.usedNoTools(), t.toolOrder([...]) | gate |
t.check(value, assertion) | t.check(t.reply, includes("sunny")) with builders from eve/evals/expect | depends on builder |
t.judge.autoevals.* | t.judge.autoevals.closedQA("cites a source") | soft |
Run-level methods observe the whole run:
| Method | Asserts |
|---|---|
t.completed() | Run did not fail and did not park on unanswered HITL input |
t.didNotFail() | No terminal failure (parked runs pass) |
t.waiting() | Run parked on HITL input |
t.messageIncludes(token) | Joined assistant text contains token |
t.outputEquals(value) / t.outputMatches(schema) | Structured output equality or Standard Schema validation |
t.calledTool(name, opts?) | Matching tool call (input, output, isError, times) |
t.notCalledTool(name) | No call to name |
t.toolOrder([...names]) | Tool names appear in order |
t.usedNoTools() | No tool calls |
t.maxToolCalls(n) | At most n tool calls |
t.calledSubagent(name, opts?) | Subagent delegation |
t.noFailedActions() | No tool, subagent, or skill action reported failure |
t.event(predicate, label) | Escape hatch over the typed event stream |
Value builders from eve/evals/expect:
| Builder | Scores | Default |
|---|---|---|
includes(substring) | substring match | gate |
equals(value) | deep structural equality | gate |
matches(schema) | Standard Schema validation | gate |
similarity(expected) | normalized Levenshtein, 0–1 | soft |
Gate vs soft
| Severity | Behavior |
|---|---|
| Gate | Failed gate → eval failed → eve eval exits non-zero |
| Soft (no threshold) | Tracked in reports; never fails |
Soft with .atLeast(n) | Below threshold → eval scored; fatal under --strict |
Override per assertion: .gate(threshold?), .soft(threshold?), .atLeast(threshold).
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
LLM-as-judge
When deterministic assertions cannot capture “good enough,” grade with t.judge.autoevals.*. The judge model is never the agent under test — Eve uses it only for scoring.
| Grader | Grades |
|---|---|
t.judge.autoevals.factuality(expected) | Factual consistency against an expected answer |
t.judge.autoevals.summarizes(expected) | Summary quality against expected text |
t.judge.autoevals.closedQA(criteria) | Free-form yes/no criterion |
t.judge.autoevals.sql(expected) | Semantic SQL equivalence |
Judge model resolution (innermost wins):
- Per-call:
t.judge.autoevals.closedQA("…", { model, modelOptions }) - Per-eval:
defineEval({ judge: { model, modelOptions }, test }) - 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
CLI flags
Exit codes and verdicts
| Exit code | Meaning |
|---|---|
0 | Every eval passed its gates (and soft thresholds under --strict) |
1 | Any eval failed — gate miss, execution error, or strict threshold miss |
2 | Configuration error — no evals discovered, no tag matches, invalid flags, missing evals.config.ts |
Per-eval verdicts:
| Verdict | Meaning |
|---|---|
passed | No execution error; every gate held; every soft threshold met |
failed | Gate assertion failed or execution errored (timeout, transport, thrown task) |
scored | Every gate held but a soft assertion fell below its threshold |
Artifacts
Each run writes artifacts under .eve/evals/<timestamp>/:
| File | Contents |
|---|---|
summary.json | Aggregated run outcome |
results.jsonl | One line per eval result |
evals/<id>.json | Per-eval assertions, verdict, logs |
evals/<id>.events.ndjson | Captured NDJSON event stream |
The console summary stays tight; when an eval fails, the artifact directory has the full event stream and assertion details.
Reporters
Eve runs and grades everything itself. Reporters ship results to external destinations.
Braintrust
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
| Symptom | Likely cause |
|---|---|
| No spans in OTel backend | Missing agent/instrumentation.ts or setup not calling registerOTel |
eve eval exits 2 with config error | Missing evals/evals.config.ts — create defineEvalConfig({}) at minimum |
| Judge assertions always fail | No judge.model resolved at any level, or missing gateway credentials |
| Eval passes locally, fails in CI | Run eve eval --strict; check model-provider credentials in the CI environment |
| Duplicate eval id error | Array-exported file collides with a nested eval path (e.g. weather.eval.ts and weather/0000.eval.ts) |
For discovery diagnostics on the agent side, run eve info and inspect .eve/discovery/diagnostics.json.
Related pages
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.mdxdocs/guides/client/streaming.mdxdocs/guides/frontend/overview.mdxpackages/eve/src/client/index.tspackages/eve/src/client/client.tspackages/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) │
└───────────────────────────────────────────────────────────────┘
| Layer | Import | Responsibility |
|---|---|---|
| TypeScript client | eve/client | Scripts, tests, evals, custom UIs without framework state |
| Framework hooks | eve/react, eve/vue, eve/svelte | Session lifecycle, optimistic projection, reactive data |
| Route mounting | eve/next, eve/nuxt, eve/sveltekit | Dev proxy / production rewrite to Eve service on same origin |
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",
});
Utility methods:
| Method | Route | Purpose |
|---|---|---|
health() | GET /eve/v1/health | Fail fast before creating a session |
info() | GET /eve/v1/info | Agent inspection payload |
fetch(path, init) | Any path on host | Authenticated escape hatch for framework-owned routes |
session(state?) | — | Create or resume a ClientSession |
Non-2xx responses throw ClientError with status and body.
ClientSession
ClientSession tracks one conversation’s continuationToken, sessionId, and streamIndex. Read session.state after each turn and serialize it to resume later.
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.
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
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.
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
| Event | UI use |
|---|---|
message.received | Confirm user message landed |
reasoning.appended | Render reasoning deltas |
message.appended | Render assistant text deltas |
actions.requested | Show tool calls |
action.result | Show tool results |
input.requested | Pause for HITL approval or question |
result.completed | Structured output when outputSchema was set |
session.waiting | Enable composer for next turn |
session.completed | Terminal conversation |
session.failed | Terminal failure |
Import helpers from eve/client:
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→ preservesessionId,continuationToken, advancestreamIndexsession.completedorsession.failed→ reset to fresh state unlesspreserveCompletedSessions: trueonsession.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 event | When emitted |
|---|---|
client.message.submitted | Optimistic user message before message.received |
client.message.failed | Submitted message failed before confirmation |
client.input.responded | HITL responses sent via send() |
events on the store snapshot is the raw server stream only. Projection events feed data through the reducer but never appear in events.
Default message reducer
defaultMessageReducer() projects into { messages: EveMessage[] } following the AI SDK UIMessage messages[].parts[] convention. Parts include user text, assistant text, reasoning, tool calls, tool results, and dynamic-tool parts with HITL metadata at part.toolMetadata?.eve?.inputRequest.
Custom reducers receive the same event union:
import { useEveAgent } from "eve/react";
import type { EveAgentReducer } from "eve/react";
const toolCounter: EveAgentReducer<{ toolCalls: number }> = {
initial: () => ({ toolCalls: 0 }),
reduce: (data, event) =>
event.type === "actions.requested" ? { toolCalls: data.toolCalls + 1 } : data,
};
const agent = useEveAgent({ reducer: toolCounter });
useEveAgent hooks
All three framework bindings wrap EveAgentStore with framework-specific reactivity. Session-shaping options (host, reducer, session, initialSession, auth, headers, maxReconnectAttempts, optimistic) are read once at store creation; remount to change them. Lifecycle callbacks refresh every render.
| Framework | Import | Return shape |
|---|---|---|
| React | eve/react | Snapshot object + send, stop, reset via useSyncExternalStore |
| Vue | eve/vue | Reactive ComputedRefs + commands; auto-imported by eve/nuxt |
| Svelte | eve/svelte | Rune-friendly getters on a reactive binding |
Returned state
| Field | Type | Purpose |
|---|---|---|
data | TData | Reducer projection (default: { messages }) |
status | "ready" | "submitted" | "streaming" | "error" | Composer gating |
error | Error | undefined | Last failure |
events | HandleMessageStreamEvent[] | Authoritative server stream |
session | SessionState | Serializable cursor |
send | (SendTurnPayload) => Promise<void> | Dispatch turn |
stop | () => void | Abort in-flight turn |
reset | () => void | Clear local state, new session |
Basic React chat
"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
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);
| Option | Default | Purpose |
|---|---|---|
eveRoot | Next.js app root | Path to Eve agent directory |
eveBuildCommand | "eve build" | Eve Vercel service build command |
configureVercelOutput | true | Write experimentalServices to .vercel/output/config.json |
servicePrefix | "/_eve_internal/eve" | Private Vercel route namespace |
devServerTimeoutMs | 180000 | Dev server startup timeout |
Local production: next build && next start serves built Eve output on port 4274 (override with EVE_NEXT_PRODUCTION_PORT). Non-Vercel hosts: set EVE_NEXT_PRODUCTION_ORIGIN.
Nuxt — eve/nuxt
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.
When to use which surface
| Use case | Surface |
|---|---|
| Browser chat UI with streaming state | useEveAgent + framework plugin |
| Backend job, eval, test harness | Client + ClientSession |
| Custom UI projection shape | useEveAgent({ reducer }) or manual for await over MessageResponse |
| Attach to in-progress stream after reload | client.session(persistedState).stream() |
| Agent discovery from a script | client.info() |
Related pages
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.mdpackages/eve/src/cli/run.tspackages/eve/src/cli/commands/register-project-commands.tspackages/eve/src/cli/commands/init.tspackages/eve/src/cli/commands/info.tspackages/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
| Command | Description |
|---|---|
eve init [target] | Scaffold a new agent, or add one to an existing project directory |
eve info | Print resolved application paths, discovered surface, messaging routes, and artifact locations |
eve build | Compile .eve/ artifacts and build host output; prints the output directory |
eve start | Serve the built .output/ app; prints the listening URL |
eve dev | Start the local dev server and open the terminal UI |
eve dev <url> | Connect the UI to an existing server URL instead of booting locally |
eve link | Link the directory to a Vercel project and pull AI Gateway credentials |
eve deploy | Deploy the agent to Vercel production (links first when needed) |
eve eval | Run evals against the local app or a remote target |
eve channels add [kind] | Scaffold a channel interactively, or by kind (slack | web) |
eve channels list | List user-authored channels |
Global flags apply to the root program:
| Flag | Description |
|---|---|
-V, --version | Print the installed eve package version |
-h, --help | Print command help |
Environment loading
eve build, eve start, eve dev, and eve eval load development environment files from the app root. Files are merged in this order (later files override earlier ones; parent-process variables set before the first load keep precedence):
.env.env.development.env.local.env.development.local
eve info, eve init, eve link, eve deploy, and eve channels do not load these files at the command handler. The dev and production servers reload env files when their watchers detect changes.
Exit codes
| Code | When |
|---|---|
0 | Command completed successfully; help requested (--help); eve eval when every eval passed |
1 | Runtime or validation error; agent-scoped command run outside an Eve project; eve link/eve deploy/eve channels failure; eve eval when any eval failed or --strict threshold miss |
2 | eve eval configuration errors (no evals discovered, no evals matching filters, invalid numeric flags) |
Uncaught errors in the top-level binary set process.exitCode = 1. eve eval calls process.exit() with the resolved code after the run completes.
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/
│ └──
Production host output lands outside .eve/:
| Path | When |
|---|---|
.output/ | Local eve build / eve start |
.vercel/output/ | Builds running with VERCEL set |
eve info prints artifact paths, workflow build directory, output directory, and the active messaging route contract.
Recommended loop
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
| Target | Behavior |
|---|---|
my-agent (name) | Scaffold a fresh project in my-agent/ |
. or existing directory | Add an agent to that project (package.json required; agent/ must not exist) |
| Omitted | Same as eve init . |
Either mode installs dependencies, initializes Git for fresh scaffolds, and runs eve dev through the project’s package manager. When launched by a coding agent, init prints the dev command instead of spawning the TUI.
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.
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>.
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.
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.
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.
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.
eve channels list
eve channels list [--json]
Lists user-authored channel names from the current project.
Related pages
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.mdpackages/eve/src/public/index.tspackages/eve/src/public/definitions/agent.tspackages/eve/src/public/definitions/tool.tspackages/eve/src/public/tools/index.tspackages/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 path | Runtime identity |
|---|---|
agent/tools/get_weather.ts | tool get_weather |
agent/connections/linear.ts | connection linear |
agent/channels/slack.ts | channel slack |
agent/skills/deploy.md | skill deploy |
evals/smoke/basic.eval.ts | eval smoke/basic |
The root agent identity is derived at compile time from manifest.agentId (package name or app-root basename), not from a field in defineAgent.
Authoring pattern
Most authored files follow the same shape: import a helper, default-export the result.
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
| Helper | Import from | Authored at |
|---|---|---|
defineAgent | eve | agent/agent.ts |
defineRemoteAgent | eve | agent/subagents/<id>/agent.ts |
defineTool | eve/tools | agent/tools/<name>.ts |
defineDynamic | eve/tools, eve/skills, eve/instructions | matching agent/ slot |
defineMcpClientConnection, defineOpenAPIConnection | eve/connections | agent/connections/<name>.ts |
defineChannel | eve/channels | agent/channels/<name>.ts |
eveChannel, slackChannel, and other platform factories | eve/channels/<platform> | agent/channels/<platform>.ts |
defineSkill | eve/skills | agent/skills/<name>.ts or .md |
defineInstructions | eve/instructions | agent/instructions.ts or agent/instructions/ |
defineHook | eve/hooks | agent/hooks/<slug>.ts |
defineSchedule | eve/schedules | agent/schedules/<name>.ts or .md |
defineState | eve/context | module scope in tools, hooks, or lifecycle code |
defineSandbox | eve/sandbox | agent/sandbox.ts |
defineInstrumentation | eve/instrumentation | agent/instrumentation.ts |
defineEval | eve/evals | evals/<path>.eval.ts |
defineEvalConfig | eve/evals | evals/evals.config.ts |
useEveAgent | eve/react, eve/vue, eve/svelte | frontend components |
defineAgent
defineAgent accepts an additive configuration object. Key fields:
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:
defineTool also exports defineDynamic (runtime resolver sentinel), disableTool() (opt out of a framework default by slug), and ExperimentalWorkflow (opt in to the experimental Workflow orchestration tool).
Tool customization factories are also exported from eve/tools: defineBashTool, defineGlobTool, defineGrepTool, defineReadFileTool, and defineWriteFileTool. Use toolResultFrom to narrow connection and MCP tool results in execute.
defineDynamic
defineDynamic registers runtime resolvers keyed on stream events. The directory you author in determines return shape and which events fire:
| Slot | Return shape | Events honored |
|---|---|---|
agent/tools/ | defineTool(...), Record<string, defineTool(...)>, or null | session.started, turn.started, step.started |
agent/skills/ | defineSkill(...), record map, or null | session.started, turn.started |
agent/instructions/ | defineInstructions({ markdown }) or null | session.started, turn.started |
Return null to contribute nothing for that event. Map keys become compound slugs (slug__key).
Other define* helpers
defineRemoteAgent— declares a remote Eve deployment the parent calls as a subagent tool. Requiresdescriptionandurl; defaultspathto/eve/v1/session. Optionalauth,headers, andoutputSchema.defineMcpClientConnection/defineOpenAPIConnection— MCP and OpenAPI integrations. Also exportsdefineInteractiveAuthorizationand connection authorization error types.defineChannel— custom ingress. Route verbsGET,POST,PUT,PATCH,DELETE, andWSare exported fromeve/channels.defineHook— stream-event subscribers underevents:. Handlers are observe-only; they cannot inject model context.defineSchedule— cron schedules with eithermarkdown(fire-and-forget) orrun(imperative handler withreceive,waitUntil,appAuth).defineState— durable typed context keyed by a namespaced string. Names must not start with the reservedeve.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-exportsisChanneland channel metadata types (canonical home iseve/channels).defineEval/defineEvalConfig— eval cases and run-wide defaults. Eval identity is path-derived; authoringidornamethrows.
Runtime context (ctx)
ctx is live only inside ALS-scoped harness execution: tool execute, hook handlers, and channel event handlers. Calling context APIs at module top level throws.
SessionContext is the shared base. ToolContext extends it with token accessors. HookContext adds agent and channel metadata.
SessionContext members
| Member | Type | Use |
|---|---|---|
session.id | string | Current session identifier |
session.auth | SessionAuth | Caller and initiator principals |
session.turn | SessionTurn | Active turn metadata |
session.parent | SessionParent? | Parent lineage for subagent sessions |
getSandbox() | Promise<SandboxSession> | Live sandbox handle; throws when unavailable |
getSkill(identifier) | SkillHandle | Named skill visible to the current agent |
ToolContext additions
| Member | Use |
|---|---|
getToken() | Resolves bearer token for the tool’s declared auth. Cache miss on interactive strategies throws ConnectionAuthorizationRequiredError, suspending the turn for OAuth. Throws when the tool has no auth. |
requireAuth() | Gates execution on authorization without resolving a token first. Always throws ConnectionAuthorizationRequiredError. |
HookContext additions
| Member | Use |
|---|---|
agent.name | Current agent name |
agent.nodeId | Optional node identifier |
channel.kind | Channel kind discriminator |
channel.continuationToken | Active continuation token when present |
Callbacks outside ALS scope (schedule run, sandbox bootstrap/onSession, instrumentation setup) receive domain-specific arguments instead of ctx.
Approval predicates
Import from eve/tools/approval. Pass the returned function to needsApproval:
import { defineTool } from "eve/tools";
import { once } from "eve/tools/approval";
export default defineTool({
description: "Deploy to production.",
inputSchema: z.object({ target: z.string() }),
needsApproval: once(),
async execute(input, ctx) {
/* ... */
},
});
| Helper | Behavior |
|---|---|
always() | Require approval on every call |
never() | Never require approval |
once() | Require approval until the user explicitly approves once per session. Keys off bare toolName, not compound approval keys. Denial leaves the tool unrecorded. |
NeedsApprovalContext exposes approvedTools (a ReadonlySet<string>), toolName, and optional toolInput for input-aware decisions.
Custom predicates can inspect toolInput for per-connection scoping or other input-dependent gates.
Built-in tool defaults
Import plain ToolDefinition values from eve/tools/defaults to spread, wrap, or patch framework tools in agent/tools/<slug>.ts:
| Export | Framework tool | Notes |
|---|---|---|
bash | Shell execution | |
readFile | File reader | Read-before-write stamps reset on compaction |
writeFile | File writer | Enforces read-before-write and stale-read detection |
glob | Path glob search | |
grep | Content regex search | |
webFetch | HTTP fetch | |
webSearch | Provider-managed search | Local execute is a throwing stub; override with defineTool in agent/tools/web_search.ts |
todo | Durable todo list | Spreading preserves framework closure-bound state |
loadSkill | Skill loader | Only useful when the agent declares skills |
Framework tools without defaults exports
The harness also ships tools that are registered internally rather than through eve/tools/defaults:
| Tool | Registration |
|---|---|
ask_question | Framework tool in the default harness |
agent | Subagent delegation (lowered from agent/subagents/) |
connection_search | Framework dynamic resolver (not a static default export) |
To remove a framework default, export disableTool() as the default from agent/tools/<matching-slug>.ts. To enable the experimental Workflow tool, re-export ExperimentalWorkflow from agent/tools/workflow.ts.
Channel auth helpers
Route-level authentication primitives live in eve/channels/auth:
| Helper | Use |
|---|---|
localDev() | Anonymous auth on loopback hosts and Vercel development |
vercelOidc(opts?) | Vercel OIDC bearer verification |
placeholderAuth() | Scaffold that fails closed in production |
httpBasic(credentials) | HTTP Basic verification |
Lower-level verifier functions (verifyHttpBasic, verifyVercelOidc, and others) are available for custom fetch handlers.
Eval entrypoints (eve/evals)
| Import path | Exports |
|---|---|
eve/evals | defineEval, defineEvalConfig, eval types, EveEvalTurnFailedError |
eve/evals/expect | includes, equals, matches, similarity assertion builders |
eve/evals/reporters | Braintrust, JUnit, EvalReporter |
eve/evals/loaders | loadJson, loadYaml |
defineEval accepts a test(t) function that drives the agent (t.send, t.respond, …) and asserts on output (t.completed(), t.check(...), t.judge.autoevals.*). Each eval file is one case; default-export an array for datasets.
defineEvalConfig is the required default export of evals/evals.config.ts. It supplies optional default judge, reporters, maxConcurrency, and timeoutMs. CLI flags and per-eval values override config defaults.
Client entrypoints (eve/client)
| Export | Role |
|---|---|
Client | HTTP client bound to one host and auth config |
ClientSession | One conversation; tracks continuation token and stream cursor |
ClientError | Non-success HTTP responses |
defaultMessageReducer | Default NDJSON stream-to-message reducer |
EveAgentStore | Stateful client store for UI integrations |
createDataUrlFilePart, createTextWithFileContent | File part helpers |
MessageResponse | Turn response wrapper |
resolveTextToResponse, resolveTextToResponses | Text-to-input-response helpers |
ClientOptions fields:
Client methods: health(), info(), createSession(), and authenticated fetch(). ClientSession.send() accepts a string shorthand or a SendTurnInput object (message, HITL responses, client context, output schema, abort signal).
The client re-exports stream event types from the protocol so consumers can type-narrow without importing internals.
Frontend hooks and framework plugins
| Import | Exports |
|---|---|
eve/react, eve/vue, eve/svelte | useEveAgent, reducer types, message types |
eve/next | withEve() Next.js config wrapper |
eve/nuxt | Nuxt module default export |
eve/sveltekit | eveSvelteKit() Vite plugin |
useEveAgent wraps Client/ClientSession with framework-native state, streaming reducers, and HITL input handling.
Imports at a glance
| Import | Holds |
|---|---|
eve | defineAgent, defineRemoteAgent, agent types |
eve/tools | defineTool, defineDynamic, disableTool, ExperimentalWorkflow, tool types, toolResultFrom, tool factories |
eve/tools/defaults | Built-in tools as plain values |
eve/tools/approval | always, once, never, NeedsApprovalContext |
eve/connections | Connection define helpers, auth types, authorization errors |
eve/channels | defineChannel, route verbs, isChannel, channel metadata types |
eve/channels/eve | eveChannel |
eve/channels/auth | Route auth strategy helpers and verifiers |
eve/channels/{slack,discord,teams,telegram,twilio,github,linear} | Platform channel factories |
eve/hooks | defineHook, HookContext |
eve/schedules | defineSchedule |
eve/skills | defineSkill, defineDynamic, SkillHandle |
eve/instructions | defineInstructions, defineDynamic |
eve/context | defineState, StateHandle, session types |
eve/sandbox | defineSandbox, backends, sandbox types |
eve/instrumentation | defineInstrumentation, isChannel |
eve/evals | Eval define helpers and types |
eve/client | Client, ClientSession, reducers, stream types |
eve/agents/auth | OutboundAuthFn for remote agent auth |
Exported types ship from the same entrypoint as the helper they describe (for example ToolDefinition from eve/tools, AgentDefinition from eve).
Related pages
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.tsdocs/concepts/sessions-runs-and-streaming.mdpackages/eve/src/internal/nitro/routes/health.tspackages/eve/src/internal/nitro/routes/info.tspackages/eve/src/internal/nitro/routes/agent-info/build-agent-info-response.tspackages/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.
| Method | Path | Auth | Availability |
|---|---|---|---|
GET | /eve/v1/health | None | Always |
GET | /eve/v1/info | localDev() then vercelOidc() | Always |
POST | /eve/v1/session | eveChannel auth chain | Always |
POST | /eve/v1/session/:sessionId | eveChannel auth chain | Always |
GET | /eve/v1/session/:sessionId/stream | eveChannel auth chain | Always |
GET or POST | /eve/v1/connections/:name/callback/:token | None (token is the capability) | Always |
POST | /eve/v1/callback/:token | None (token is the capability) | Always |
GET | /eve/v1/dev/runtime-artifacts | None | Dev only |
POST | /eve/v1/dev/runtime-artifacts/rebuild | None | Dev only |
POST | /eve/v1/dev/schedules/:scheduleId | None | Dev only |
Authored channels (defineChannel, Slack, Discord, and others) register additional routes outside this table. This page covers only the framework-owned /eve/v1 surface.
Authentication
Routes fall into three auth classes:
- Always public —
GET /eve/v1/health, OAuth/callback routes, and all dev-only routes. Callback routes are intentionally unauthenticated: the unguessable:tokensegment is the capability that authorizes resuming parked workflow work. - Default inspection auth —
GET /eve/v1/infowalks[localDev(), vercelOidc()]. Loopback requests succeed locally; deployed Vercel targets require a valid OIDC bearer. - Channel-configured auth — Session routes run the
autharray you pass toeveChannel.routeAuthwalks the array in order; the first strategy returning aSessionAuthContextwins. Skipped entries (null/undefined) continue to the next. Exhaustion returns401. Includenone()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 onPOST /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 thex-eve-session-idresponse 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>"
}
:::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:
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.
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": "…"
}
| Status | Meaning |
|---|---|
202 | Session created and turn enqueued |
400 | Invalid JSON, missing message, bad attachment, or upload policy violation (413 too large, 415 bad media type) |
401 / 403 | Auth walk failed |
204 | onMessage returned null — message accepted but not dispatched |
500 | onMessage handler threw |
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. :::
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. :::
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
| Header | Value |
|---|---|
content-type | application/x-ndjson; charset=utf-8 |
x-eve-stream-format | ndjson |
x-eve-stream-version | 15 |
x-eve-session-id | Session id |
cache-control | no-store, no-transform |
x-accel-buffering | no |
Each line is one JSON object. Persisted events include meta.at (ISO timestamp). The stream is durable and fully replayable.
Turn boundary events
Stop reading (or reconnect with an updated startIndex) when you see:
session.waiting— parked, ready for the next user messagesession.completed— terminal successsession.failed— terminal failure
NDJSON event vocabulary
Events are typed by the type field. Most carry data.sequence, data.turnId, and often data.stepIndex for ordering within a turn.
Session and turn lifecycle
| Event | Meaning |
|---|---|
session.started | Durable session created. data.runtime may include agent id, model id, Eve version, and build metadata. Child subagent sessions include data.invocation. |
turn.started | New turn began (data.turnId, data.sequence). |
message.received | Inbound user message accepted (data.message is a text summary for multimodal input). |
turn.completed | Turn finished successfully. |
turn.failed | Turn failed (data.code, data.message, optional data.details). |
session.waiting | Parked with data.wait: "next-user-message". |
session.completed | Terminal session success. |
session.failed | Terminal session failure (data.code, data.message, data.sessionId). |
Model steps
| Event | Meaning |
|---|---|
step.started | Model call began. |
step.completed | Model call finished (data.finishReason, optional data.usage token counts). |
step.failed | Model call failed (data.code, data.message). |
finishReason values: stop, tool-calls, length, content-filter, error, other. tool-calls is the only non-terminal assistant step in the default harness.
Assistant output
| Event | Meaning |
|---|---|
message.appended | Text delta (data.messageDelta, cumulative data.messageSoFar). |
message.completed | Finalized text block (data.message, data.finishReason). May fire multiple times per turn. |
reasoning.appended | Reasoning delta (data.reasoningDelta, data.reasoningSoFar). |
reasoning.completed | Finalized reasoning block. |
result.completed | Structured output when outputSchema was requested (data.result). |
Tools and human input
| Event | Meaning |
|---|---|
actions.requested | Model requested tool calls (data.actions). |
action.result | Tool result (data.result, data.status: "completed" | "failed", optional data.error). |
input.requested | Run paused for HITL approval or ask_question (data.requests). |
Each InputRequest includes requestId, prompt, optional options (id, label, description, style), display hint (confirmation | select | text), and the underlying action.
Subagents
| Event | Meaning |
|---|---|
subagent.called | Child workflow session started (data.childSessionId — attach to that session’s stream). |
subagent.started | Inline subagent execution began. |
subagent.event | Wraps a child stream event under data.event (inline subagents). |
subagent.completed | Inline subagent finished (data.output). |
Delegated workflow subagents publish progress on their own child-session stream. The parent only signals attachment via subagent.called.
Connections and compaction
| Event | Meaning |
|---|---|
authorization.required | Connection needs OAuth (data.name, data.description, data.authorization challenge with optional url, userCode, expiresAt, instructions, displayName). |
authorization.completed | Authorization resolved (data.outcome: authorized | declined | failed | timed-out). |
compaction.requested | Context compaction began (data.modelId, data.usageInputTokens). |
compaction.completed | Compaction checkpoint written. |
:::endpoint GET|POST /eve/v1/connections/:name/callback/:token OAuth IdP redirect target for in-turn connection authorization. :::
:name is the path-derived connection name. :token is the workflow hook token minted when authorization starts. The handler projects query (and form-post body) params into an authorizationCallback payload, calls resumeHook(token, …), and returns an HTML “Authorization complete” page.
No Eve credentials are required — the token is the unguessable capability. Returns 404 when no matching authorization is pending.
:::endpoint POST /eve/v1/callback/:token Terminal callback for remote subagent sessions. :::
Used by defineRemoteAgent when the framework POSTs completion results back to the parent agent. Body shape:
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:
| Status | Typical cause |
|---|---|
400 | Malformed body, missing required fields, invalid startIndex |
401 / 403 | Auth walk failed or forbidden |
404 | Unknown session, schedule, or expired callback token |
413 / 415 | Attachment exceeds size cap or violates media-type policy |
500 | Handler threw (errorId may be present for support correlation) |
Client integration
eve/client wraps these routes with typed Client and ClientSession helpers, automatic stream reconnection, and continuation-token management. Framework plugins (eve/next, eve/nuxt, eve/sveltekit) can proxy same-origin. Prefer the client SDK over hand-rolling POST + NDJSON loops for scripts, tests, and custom UIs.
# 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
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.mddocs/reference/cli.mdpackages/eve/src/cli/commands/info.tspackages/eve/src/compiler/artifacts.tspackages/eve/src/execution/runtime-errors.tspackages/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.
Inspectable artifacts
Eve preserves discovery and compile artifacts under .eve/:
:::files .eve/ ├── discovery/ │ ├── agent-discovery-manifest.json # what Eve found on disk │ └── diagnostics.json # authored-shape errors and warnings └── compile/ ├── compiled-agent-manifest.json # serialized surface the runtime loads ├── compile-metadata.json # build metadata, hashes, compile status └── module-map.mjs # compiled module entrypoints :::
| Artifact | Path | Use when |
|---|---|---|
| Discovery manifest | .eve/discovery/agent-discovery-manifest.json | Confirm a file was found on disk |
| Diagnostics | .eve/discovery/diagnostics.json | Read structured errors and warnings |
| Compiled manifest | .eve/compile/compiled-agent-manifest.json | See what the runtime actually serves |
| Compile metadata | .eve/compile/compile-metadata.json | Check status and diagnostic summary counts |
| Module map | .eve/compile/module-map.mjs | Trace compiled import entrypoints |
eve info --json includes an artifacts object with absolute paths to each file when compile state is available.
diagnostics.json schema
Each diagnostics artifact is versioned and machine-readable:
code— stable machine-readable identifier (for examplediscover/required-instructions-missing)message— human-readable explanationseverity—"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.
| Code | Severity | Typical cause |
|---|---|---|
discover/project-not-found | error | No agent/ directory resolved from the app root |
discover/required-instructions-missing | error | Missing instructions.md or instructions.ts |
discover/deprecated-system-slot | warning | Fallback to deprecated system.{md,ts} — rename to instructions.* |
discover/slot-collision | error | Same slot has both markdown and module sources |
discover/module-slot-collision | error | Multiple module sources compete for one slot |
discover/tool-name-invalid | error | agent/tools/<name>.ts filename violates tool slug charset |
discover/connection-name-invalid | error | Connection filename violates kebab-case slug rule |
discover/channel-name-invalid | error | Channel filename or directory segment violates slug rule |
discover/hook-name-invalid | error | Hook filename or directory segment violates slug rule |
discover/tools-directory-invalid | error | agent/tools/ exists but is not a directory |
discover/channels-directory-invalid | error | agent/channels/ exists but is not a directory |
discover/hooks-directory-invalid | error | agent/hooks/ exists but is not a directory |
discover/connections-directory-invalid | error | agent/connections/ exists but is not a directory |
discover/sandbox-directory-invalid | error | agent/sandbox/ exists but is not a directory |
discover/skills-directory-invalid | error | agent/skills/ exists but is not a directory |
discover/schedules-directory-invalid | error | Root schedules/ exists but is not a directory |
discover/unsupported-directory | warning | Ignored directory under agent/ |
discover/skill-collision | error | Duplicate skill name |
discover/skill-markdown-missing | error | Packaged skill directory missing SKILL.md |
discover/skill-frontmatter-invalid | error | Invalid YAML frontmatter in a skill |
discover/connection-file-folder-collision | error | Both file and folder exist for one connection name |
discover/connection-folder-empty | error | Connection folder has no module source |
discover/sandbox-folder-empty | error | Sandbox folder has no module source |
discover/required-subagent-config-module-missing | error | Subagent missing required agent.ts |
discover/local-subagent-schedules-invalid | error | Schedules under a subagent (root-only slot) |
discover/schedule-file-unsupported | error | Unsupported file type under schedules/ |
Common failure modes
| Symptom | Likely cause and fix |
|---|---|
| Tool not discovered (model never sees it) | Run eve info. Confirm the file is in agent/tools/<name>.ts and default-exports defineTool(...). Check .eve/discovery/diagnostics.json. Remember schedules/ is root-only. |
| Model won’t call a tool it should | Tighten the tool description and inputSchema; put procedural guidance in a skill, not the description. Confirm the tool appears in eve info. |
Stuck on session.waiting | Turn is parked on approval, input.requested, or connection sign-in. Respond via the client, or POST a follow-up with the current continuationToken. A stale token is rejected. |
401 on production routes | Expected fail-closed auth. Replace placeholderAuth(), set VERCEL_PROJECT_ID, and configure environment so vercelOidc() accepts user tokens. See Auth and deployment. |
| Build fails with discovery errors | Read the printed diagnostics and .eve/discovery/diagnostics.json. Validate root-vs-subagent boundaries and that secrets come from env vars, not authored files. |
| AI Gateway auth errors at runtime | Missing or stale AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN. Run eve link or set credentials in .env.local. In eve dev, use /model to refresh. |
eve eval exits 2 | Configuration error (missing evals, invalid flags, unreachable target). Exit 1 means a failed check or execution error; exit 0 means all passed. |
Dev server PID conflicts
Local eve dev writes the active server process ID to .eve/dev-process.pid. Before starting, Eve checks whether that PID is still running.
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).
| Label | Fix command | When it fires |
|---|---|---|
model provider not linked | /model | No .vercel/project.json and no gateway credentials in env |
AI Gateway credentials missing | /model | Project is linked but neither AI_GATEWAY_API_KEY nor VERCEL_OIDC_TOKEN is set |
not logged in | /login | Off-critical-path vercel whoami probe reports logged out |
Vercel CLI not found | /vc | vercel binary absent from PATH |
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 --> [*]
| Cascade | Events | Session state after |
|---|---|---|
| Terminal | step.failed → turn.failed → session.failed | Dead — no further follow-up on the same continuation token |
| Recoverable | step.failed → turn.failed → session.waiting | Parked — send another message with the current continuationToken |
| Success (conversation) | turn.completed → session.waiting | Ready for next user message |
| Success (task) | turn.completed → session.completed | Terminal success |
session.waiting with { "wait": "next-user-message" } is normal between turns. It also follows recoverable failures so you can retry after fixing credentials or transient provider errors.
Parked work that emits session.waiting without a failure cascade includes HITL approvals (input.requested), ask_question, and connection OAuth (authorization.required).
Runtime error codes
Failure events carry { code, message, details? }. The code is the stable identifier; details may include errorId (for log correlation), gateway metadata, or workflow-stream diagnostics.
step.failed / turn.failed / session.failed codes
| Code | Terminal or recoverable | Meaning |
|---|---|---|
MODEL_CALL_FAILED | Both — terminal for config errors; recoverable for transient provider failures in conversation mode | Model call failed. Check details for gatewayName, statusCode, errorId. Gateway auth failures include GatewayAuthenticationError or summary name AI Gateway authentication failed. |
WORKFLOW_STREAM_WRITE_FAILED | Recoverable | Durable event-stream flush to the workflow server failed (timeout, 5xx). details includes operation, statusCode, url, vercelId, vercelError. The model call may have succeeded. |
OUTPUT_SCHEMA_NOT_FULFILLED | Terminal in task mode; recoverable in conversation mode | Agent could not produce output matching outputSchema / final_output. |
SESSION_FAILED | Terminal | Session callback step failed. |
REMOTE_AGENT_START_FAILED | Terminal | Remote agent dispatch could not start. |
REMOTE_AGENT_FAILED | Terminal | Remote agent callback reported failure. |
SUBAGENT_EXECUTION_FAILED | Surfaced on parent via action.result | Delegated subagent run failed; appears in subagent-result output, not always as a root-session step.failed. |
Action and tool result codes
These appear on action.result events (and in tool-result message parts), not necessarily as step.failed codes:
| Code | Meaning |
|---|---|
TOOL_EXECUTION_DENIED | HITL approval denied or tool execution blocked |
TOOL_EXECUTION_FAILED | Tool returned an error-shaped JSON output ({ code, message }) |
ACTION_RESULT_FAILED | Fallback when an action result is marked isError without a structured code |
Continuation token errors
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:
| Surface | Where | What it shows |
|---|---|---|
| NDJSON stream | GET /eve/v1/session/:sessionId/stream | Live step.failed, turn.failed, session.failed, tool events |
| Workflow run tags | Vercel Workflow dashboard ($eve.*) | Session/turn tree, model id, token counts — no instrumentation.ts required |
| OpenTelemetry | agent/instrumentation.ts | AI SDK spans when configured |
See Instrumentation and evals for tracing setup and workflow run tags.
Related pages
Similar Articles
I built a local-first autonomous coding agent with a cyberpunk soul — Eve Agent V2 Unleashed (open source)
Eve Agent V2 Unleashed is an open-source autonomous coding agent that runs locally via Ollama, featuring a 40-round tool loop, 112 sub-agents, and optional cloud escalation. It can plan, write, test, and verify code with zero hand-holding, with quick start in under 5 minutes.
@sashimikun_void: Found an interesting open-source all-in-one comms + crm + agent OSS project @macrodotcom yesterday. I was very impresse…
Introduces grok-wiki v0.0.16, a tool that automatically generates documentation from a GitHub URL or folder path, enabling agents to read docs. The author also highlights the Macro open-source comms/CRM/agent project.
@browser_use: Introducing B, a browser agent template! Built on Eve by @vercel. Give any agent a real Browser Use Cloud browser. Watc…
Introducing B, an open-source browser agent template built on Eve by Vercel that uses Browser Use Cloud to give any AI agent a real web browser. It includes a chat UI and live browser viewing.
@rauchg: Vercel CLI as a self-updating binary with zero external dependencies. Our CLI is one of the key interfaces enabling the…
Vercel CLI now ships as a self-updating native binary with zero external dependencies, improving security, startup speed, and reducing footprint by ~80%.
@sashimikun_void: Grok-Wiki now supports public sharing! Fun fact: Agent Memory (yesterday's #1 Trending Repo on GitHub) actually adopts …
Grok-Wiki now supports public sharing; Agent Memory, a trending GitHub repo, uses the iii-architecture in its stack.