eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
252 lines (179 loc) • 14.3 kB
text/mdx
---
title: "eve"
description: "The default HTTP API for an agent, covering session routes, auth, and customization."
---
The eve channel is the framework's default HTTP API. It's what the terminal UI, [`useEveAgent`](../guides/frontend/overview), `curl`, and any SDK client talk to when they start sessions, send messages, and stream events. The selected `channels/eve.ts` source owns the complete `/eve/v1` surface, including health, inspection, callbacks, task input, and session routes. eve supplies that source when `agent/channels/eve.ts` does not exist.
Every running eve app exposes its own API. `eve.dev` publishes framework documentation; it is not a shared API, authorization server, MCP server, or A2A server. Each deployment supplies its own host and authentication policy.
Reach for it when something needs HTTP access to your agent, including local tooling, a browser frontend, the terminal UI, or another API client. Most apps never write this file. Add `agent/channels/eve.ts` only to override the defaults, usually the route auth policy.
```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";
export default eveChannel({
auth: [vercelOidc(), localDev()],
});
```
## Routes
The default eve channel inspects the agent, creates sessions, accepts callbacks and task input, controls sessions, and streams events. Its public routes include:
- `GET /eve/v1/health` (check whether the application is reachable)
- `GET /eve/v1/info` (inspect the agent)
- `POST /eve/v1/session` (create a session, optionally sending its first message)
- `POST /eve/v1/session/:sessionId` (send a follow-up)
- `POST /eve/v1/session/:sessionId/cancel` (cancel the in-flight turn)
- `POST /eve/v1/session/:sessionId/clear` (clear the session's model history)
- `POST /eve/v1/session/:sessionId/compact` (compact the session's context)
- `POST /eve/v1/session/:sessionId/reset` (retire the session)
- `GET /eve/v1/session/:sessionId/stream` (stream events as NDJSON)
The session routes use only durable session IDs. Create a session explicitly, then put its returned ID in every follow-up, control, and stream path.
`GET /eve/v1/health` is public and returns `{ ok: true, status: "ready", workflowId: string }`. `GET /eve/v1/info` uses the channel's auth policy and returns agent-info version 6. The TypeScript client validates both successful payloads: malformed health JSON throws `HealthResponseError`, malformed inspection JSON throws `AgentInfoResponseError`, and a non-success response from either route throws `ClientError`.
### Start and continue a session
Create a conversation session before its first message with a bodyless request:
```bash
curl -X POST https://<deployment>/eve/v1/session
# {"ok":true,"sessionId":"wrun_A","status":"accepted"}
```
See [Start a session](../concepts/sessions-runs-and-streaming#start-a-session) for the
initialization boundary, first-turn identity, readiness response, and retry behavior.
The create request still requires route auth. Session audience and the timeout are selected
at creation; they do not change when the first message arrives. Task mode and turn-scoped
fields such as `clientContext` and `outputSchema` require a message on the create request.
Include an initial message to create the session and start its first turn in one request:
```bash
curl -X POST https://<deployment>/eve/v1/session \
-H "Content-Type: application/json" \
-d '{"message":"What is the weather in Paris?"}'
# {"ok":true,"sessionId":"wrun_A","status":"accepted"}
```
Authenticated callers that may retry a create request can pass their own `operationId` for
create-once semantics. Once the operation owner is active, the same operation under the same
authenticated principal returns that session instead of dispatching the input again. The create
route does not wait for a concurrently starting request to publish ownership: simultaneous
requests can receive different accepted candidate IDs, while only the candidate that claims the
operation runs its first turn. Retry the operation after startup when you need its canonical
session ID. Anonymous callers cannot use `operationId`, and operation ownership expires when the
session is no longer resumable.
```bash
curl -X POST https://<deployment>/eve/v1/session \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"message":"What is the weather in Paris?","operationId":"order-4213-research"}'
# {"ok":true,"sessionId":"wrun_A","status":"accepted"}
```
When the create request includes a message, a follow-up request accepts exactly one of
`message` or `inputResponses`; use the latter to answer a pending HITL request:
```bash
curl -X POST https://<deployment>/eve/v1/session/wrun_A \
-H "Content-Type: application/json" \
-d '{"inputResponses":[{"requestId":"req_A","optionId":"approve"}]}'
```
Follow-up messages use `turnPolicy: "steer"` by default. Before assistant answer output or local tool execution begins, steering interrupts pending model generation and applies the correction in the same turn. Executing tools finish and checkpoint their results first. After answer output starts, steering waits for the next committed workflow boundary. Reasoning and search progress do not count as answer output; interrupted provider-managed search may run again. The turn ID stays the same.
Set `turnPolicy: "queue"` on `eveChannel(...)` when follow-ups should wait for active turns to finish. `inputResponses` answer their addressed requests.
Sending a message to an unknown or terminal session ID returns `409` with
`{"code":"session_not_active","error":"The session is no longer active.","ok":false}`.
An existing workflow whose inbox is not yet available returns `409 session_not_ready` instead.
TypeScript clients expose the stable code as `ClientError.code`. The route never
creates or follows a replacement session.
### Stream events
Stream a session as newline-delimited JSON from `GET /eve/v1/session/:sessionId/stream`. The [session protocol](../concepts/sessions-runs-and-streaming#stream-a-session) defines the event set, envelopes, cursors, and reconnection behavior.
### Cancel a turn
Post to `/eve/v1/session/:sessionId/cancel` to request cancellation of the active turn. You can include the observed `turnId` to keep a late request from cancelling a newer turn. Include `tasks: true` to also cancel every background task owned by the session, including while the session is parked. Cancellation is asynchronous; confirm the turn boundary on the stream as `turn.cancelled` followed by `session.waiting`, and inspect task state in a later turn to confirm task cancellation.
See [Cancel the in-flight turn](../concepts/sessions-runs-and-streaming#cancel-the-in-flight-turn) for response statuses, HTTP status codes, subagent cancellation, and race behavior.
### Clear context
Post to `/eve/v1/session/:sessionId/clear` to remove model-message history while preserving the session ID, system prompt, tools, skills, durable state, limits, and sandbox. See [Compact, clear, and reset](../concepts/sessions-runs-and-streaming#compact-clear-and-reset).
### Compact context
Post to `/eve/v1/session/:sessionId/compact` to summarize context without sending a user message. The operation waits for an active turn to settle and reports its result on the stream. See [Compact, clear, and reset](../concepts/sessions-runs-and-streaming#compact-clear-and-reset).
### Reset a session
Post to `/eve/v1/session/:sessionId/reset` to terminally retire that session. Reset never replaces the ID automatically; create a new session explicitly for a fresh conversation. See [Compact, clear, and reset](../concepts/sessions-runs-and-streaming#compact-clear-and-reset).
## Replace or disable the defaults
An authored `agent/channels/eve.ts` replaces the complete default eve channel. An `eveChannel(...)` replacement keeps the standard route set with your options; a custom `defineChannel(...)` replacement exposes only the routes you declare. Health, inspection, and callbacks do not reappear through a hidden host fallback.
Disable the complete surface by exporting `disableRoute()` at that slot:
```ts title="agent/channels/eve.ts"
import { disableRoute } from "eve/channels";
export default disableRoute();
```
The default home page is a separate `channels/home.ts` source that serves `GET /` and `HEAD /`. Author `agent/channels/home.ts` to replace it or export `disableRoute()` there to remove it without affecting `/eve/v1`.
## CORS
The eve channel leaves CORS untouched by default. Pass `cors: true` to enable
permissive browser CORS with preflight handling, or pass an options object to
narrow origins, methods, and headers. Route auth still runs on the actual
session requests.
Enable or narrow CORS only when browser clients call the channel directly:
```ts title="agent/channels/eve.ts"
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";
export default eveChannel({
auth: [vercelOidc(), localDev()],
cors: {
origin: "https://app.example.com",
methods: ["GET", "POST"],
allowedHeaders: ["authorization", "content-type"],
},
});
```
## Authentication
The `auth` option decides who can call `/eve/v1/info` and the session routes. The built-in helpers cover development and trusted infrastructure:
- `localDev()` accepts requests during local development.
- `vercelOidc()` lets the local CLI reach a deployed agent, and lets other internal deployments from your team call it.
Neither admits browser users or external clients in production. For a public app, wire the channel to your own auth (Clerk, Auth.js, your own OIDC/JWT verification, an API-key verifier, or any custom `AuthFn`). Vercel OIDC is optional; use it only when Vercel-issued deployment tokens are part of your trust model.
`eve init` scaffolds an `agent/channels/eve.ts` with a production placeholder so you replace it before going live. The generated channel checks Vercel OIDC before falling back to localhost access, and includes `placeholderAuth()`, which returns a setup-focused 401 in production until you swap it for real auth. Delete the file and eve selects its default channel source with `[vercelOidc(), localDev(), placeholderAuth()]`, which rejects all production traffic.
For the full auth model and helper list, see [Auth & route protection](../guides/auth-and-route-protection).
## Audience
The eve channel assigns an observability audience when it creates a session. The
audience controls whether instrumentation may capture session content:
| Session creator | Default audience |
| ----------------------------------------- | ---------------- |
| Anonymous caller | `unknown` |
| `user`, `service`, or `runtime` principal | `private` |
| Any other principal type | `unknown` |
Anonymous HTTP surfaces are `unknown` by default because reachability does not establish that their content is safe to record. Authenticated `user`, `service`, and `runtime` sessions are private because they belong to one identified party. Other authenticated principal types also remain `unknown`: trace consumers record metadata but omit content by default in preview and production. Set an explicit `audience: "public"` only for intentionally public traffic.
For a production endpoint whose conversations are all intentionally public,
set a constant audience:
```ts title="agent/channels/eve.ts"
import { vercelOidc } from "eve/channels/auth";
import { eveChannel } from "eve/channels/eve";
export default eveChannel({
auth: [vercelOidc()],
audience: "public",
});
```
`audience` controls observability content capture; it does not change who may
call the endpoint. `auth` still controls access. For a classification that
depends on channel state or application-specific verified data, use a [custom
channel audience](./custom#conversation-audience).
You can also pass a function that receives `caller`, channel, run mode, and
deployment environment. Classification is fixed at session creation, so a later
turn from a different caller does not reclassify the session.
For a remote agent, a trusted dispatch can forward the original audience and
its trace-content ceiling. See [Preserving trace
content](../guides/remote-agents#preserving-trace-content).
## Customization
Use `onMessage` to add request-specific context before the agent sees the user message, and `events` to observe stream events from sessions this channel created:
```ts title="agent/channels/eve.ts"
import { eveChannel, defaultEveAuth } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";
export default eveChannel({
auth: [vercelOidc(), localDev()],
onMessage(ctx, message) {
const callerId = ctx.eve.caller?.principalId ?? "anonymous";
return {
auth: defaultEveAuth(ctx),
context: [`HTTP caller ${callerId} sent: ${message}`],
};
},
events: {
"message.completed"(eventData, _channel, ctx) {
console.log("eve response completed", {
sessionId: ctx.session.id,
});
},
},
});
```
`onMessage` must return an auth result. Return `title` alongside `auth` to set the title when the dispatch creates a session or sends the first message to a prewarmed session. A successful canonical eve HTTP message always dispatches and therefore always produces or continues a session.
## Clients
The browser side of this API lives in the [Frontend](../guides/frontend/overview) docs, where `useEveAgent` drives the eve channel from React UI.
For scripts, server-to-server calls, evals, tests, and custom clients, use the [Client SDK](../guides/client/overview). It wraps the ID-addressed session routes, stream cursor, and reconnect loop.
## What to read next
- [Frontend](../guides/frontend/overview): drive the eve channel from browser UI with `useEveAgent`
- [Client SDK](../guides/client/overview): call the eve channel from TypeScript
- [Auth & route protection](../guides/auth-and-route-protection): the route auth policy
- [Sessions, runs & streaming](../concepts/sessions-runs-and-streaming): the routes this channel exposes