Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f621472
chore(api): snapshot public export names before surface cleanup
vercel[bot] Oct 17, 2026
d69ecd8
refactor(api)!: curate exports and finalize v27 authoring contracts
vercel[bot] Oct 17, 2026
d80e901
fix(api): align create receipts and command migration documentation
vercel[bot] Oct 17, 2026
8ba935c
fix(fixtures): follow command receipts and canonical interaction options
vercel[bot] Oct 17, 2026
f039502
fix(runtime): retain answer deliveries through resumed workflow work
vercel[bot] Oct 17, 2026
0cbbaa4
fix(evals): follow exact delivery settlement past unrelated turn boun…
vercel[bot] Oct 17, 2026
8e19b89
fix(runtime): bind task-wait answers to the waiting turn delivery
vercel[bot] Oct 17, 2026
9741889
fix(fixtures): observe queued budget work before answering its prompt
vercel[bot] Oct 17, 2026
4f9d765
fix(runtime): preserve authenticated identity on the initial delivery
vercel[bot] Oct 17, 2026
c9758ad
fix(runtime): accept authenticated workflow answers and remote receipts
vercel[bot] Oct 17, 2026
8c407f0
fix(runtime): read delivery receipts from remote control routes
vercel[bot] Oct 17, 2026
974267d
fix(runtime): recognize admitted relayed answers as read input
vercel[bot] Oct 17, 2026
e93b899
fix(evals): retain intervening facts and previously open interactions
vercel[bot] Oct 17, 2026
f69749b
fix(evals): separate cursor catch-up from delivery event slices
vercel[bot] Oct 17, 2026
86ebf79
fix(api): finish authorization docs and remove orphan verifier options
vercel[bot] Oct 17, 2026
c750c1b
chore(api): refresh assembled session contracts after restacking
vercel[bot] Oct 17, 2026
7484f51
fix(api): preserve delegated create acknowledgements and migrate docs
vercel[bot] Oct 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/authenticated-workflow-answers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Fix authenticated answers to workflow questions being discarded by an outdated control-message validator. Remote agent dispatch now accepts named delivery receipts while preserving support for older peers' creation acknowledgements.
2 changes: 1 addition & 1 deletion .changeset/command-deliveries.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"eve": minor
---

Session commands now return serializable, followable `Delivery` receipts, with request statuses distinct from session termination and explicit retryability on `SessionNotActiveError`. Session handles use `id`/`stream()` with one line-indexed `SessionStreamOptions`, `SessionInfo` matches `ctx.session`, client handles are type-only exports, local resets become `newSession()`, eval/CLI results use `reply`/`data`, and `invokeTool` returns call-shaped outcomes.
Session commands now return serializable, followable `Delivery` receipts, with request statuses distinct from session termination and explicit retryability on `SessionNotActiveError`; answers follow the turn work they resume. Session handles use `id`/`stream()` with one line-indexed `SessionStreamOptions`, `SessionInfo` matches `ctx.session`, client handles are type-only exports, local resets become `newSession()`, eval/CLI results use `reply`/`data`, and `invokeTool` returns call-shaped outcomes.
5 changes: 5 additions & 0 deletions .changeset/curated-public-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": minor
---

Curate public exports and give history and sandbox APIs eve-owned types while retaining reusable authoring types. Use canonical import paths, `principalType` for connection authorization, `ChannelCors` across channels, and explicit local or remote self-modification mounts; scaffold integrations now live under `eve/internal/*`, and verifier option types used only by internal helpers are no longer exported.
5 changes: 5 additions & 0 deletions .changeset/delegated-create-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Delegated session creation retains the accepted acknowledgement required by supported remote protocols. Browser/client creates still return the new delivery receipt without the legacy status field.
5 changes: 5 additions & 0 deletions .changeset/eval-interaction-cursor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Keep eval session views in sync with all facts since their last read, including interactions published between deliveries. Answer results retain still-open questions from earlier reads, so nested approvals cannot be lost when a receipt settles.
5 changes: 5 additions & 0 deletions .changeset/initial-delivery-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Preserve the authenticated session creator on the first message delivery. Initial tools and delegated work now run with the same caller identity as authenticated follow-up messages instead of losing it during admission.
5 changes: 5 additions & 0 deletions .changeset/relayed-partial-approval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Settle a relayed answer delivery as awaiting input when its asker admits the answer but still needs another approval. Parents no longer hang waiting for new model work after the child has already read the answer.
5 changes: 5 additions & 0 deletions .changeset/remote-control-receipts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Accept delivery receipts from remote cancellation and reset routes, while continuing to read older peers' acknowledgements. Remote control responses still must name the requested session.
2 changes: 1 addition & 1 deletion apps/docs/registry/eve/self-modification/extension.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import selfModification from "eve/self-modification";
import selfModification from "eve/self-modification/local";

export default selfModification({
// model: "provider/model",
Expand Down
4 changes: 2 additions & 2 deletions apps/fixtures/agent-tui-client/agent/channels/anchored.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default defineChannel({
ok: true,
anchorToken,
initialContinuationToken: `pending:${body.threadId}`,
sessionId: session.id,
sessionId: session.sessionId,
});
}),

Expand All @@ -75,7 +75,7 @@ export default defineChannel({
state: initialState(anchorToken),
});

return Response.json({ ok: true, anchorToken, sessionId: session.id });
return Response.json({ ok: true, anchorToken, sessionId: session.sessionId });
}),
],
events: {
Expand Down
32 changes: 25 additions & 7 deletions docs/channels/custom.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export default defineChannel({
return Response.json(await source.reset({ reason: "User requested /new" }));
}

const session = await source.send(body.message, { auth: null });
return Response.json({ sessionId: session.id });
const delivery = await source.send(body.message, { auth: null });
return Response.json(delivery, { status: 202 });
}),

POST("/threads/:threadId/cancel", async (_request, { from, params }) =>
Expand All @@ -44,8 +44,26 @@ export default defineChannel({
Response.json(await from(params.threadId).clear()),
),

GET("/sessions/:sessionId/stream", async (_request, { attachSession, params }) => {
const stream = await attachSession(params.sessionId).getEventStream();
GET("/sessions/:sessionId/stream", async (request, { attachSession, params }) => {
const iterator = attachSession(params.sessionId)
.stream({ signal: request.signal })
[Symbol.asyncIterator]();
// This custom event-NDJSON route is not the built-in eve/client wire endpoint.
// Read before sending headers so stranded-session errors reach the route.
const first = await iterator.next();
let firstPending = true;
const encoder = new TextEncoder();
const stream = new ReadableStream<Uint8Array>({
async pull(controller) {
const next = firstPending ? first : await iterator.next();
firstPending = false;
if (next.done) controller.close();
else controller.enqueue(encoder.encode(JSON.stringify(next.value) + "\n"));
},
async cancel() {
await iterator.return?.();
},
});
return new Response(stream, {
headers: { "content-type": "application/x-ndjson; charset=utf-8" },
});
Expand Down Expand Up @@ -161,12 +179,12 @@ await session.cancel({ turnId });
await session.compact();
await session.clear();
await session.reset({ reason: "Retire this session" });
await session.getEventStream({ startIndex: 12 });
for await (const event of session.stream({ startIndex: 12 })) render(event);
```

For a stranded session, `Session.send()`, `Session.respond()`, and a following
`Session.getEventStream()` throw `SessionStrandedError` from `eve/channels` and deliver nothing.
`getEventStream({ follow: false })` still reads the recorded history up to the current tail without
read through `Session.stream()` throw `SessionStrandedError` from `eve/channels` and deliver nothing.
`stream({ follow: false })` still reads the recorded history up to the current tail without
changing the session. `error.owner.eveVersion` names the eve release that built the session when it recorded one. Call
`reset()` to end the stranded session, then start a new one:

Expand Down
10 changes: 5 additions & 5 deletions docs/channels/eve.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ 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"}
# {"ok":true,"sessionId":"wrun_A"}
```

See [Start a session](../concepts/sessions-runs-and-streaming#start-a-session) for the
Expand All @@ -124,7 +124,7 @@ Include an initial message to create the session and start its first turn in one
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"}
# {"ok":true,"sessionId":"wrun_A","deliveryId":"delivery_A"}
```

Authenticated callers that may retry a create request can pass their own `operationId` for
Expand All @@ -141,7 +141,7 @@ 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"}
# {"ok":true,"sessionId":"wrun_A","deliveryId":"delivery_A"}
```

When the create request includes a message, a follow-up request accepts exactly one of
Expand Down Expand Up @@ -209,7 +209,7 @@ After a session ends, the World keeps its stream only for its retention period.

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. Cancellation is asynchronous; confirm it on the stream as `turn.settled` with `outcome: "cancelled"`.

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.
See [Cancel the in-flight turn](../concepts/sessions-runs-and-streaming#cancel-the-in-flight-turn) for delivery receipts, HTTP status codes, subagent cancellation, and race behavior.

### Clear context

Expand Down Expand Up @@ -255,7 +255,7 @@ export default eveChannel({
cors: {
origin: "https://app.example.com",
methods: ["GET", "POST"],
allowedHeaders: ["authorization", "content-type"],
allowHeaders: ["authorization", "content-type"],
},
});
```
Expand Down
6 changes: 3 additions & 3 deletions docs/channels/slack.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ export default slackChannel({

With `"queue"`, adjacent messages with matching full auth contexts can combine into one turn, keeping their attachments and context in order. Messages from different users, or from the same user with different authorization attributes, stay separate. Anonymous deliveries also stay separate. Batching follows session-inbox order, and turns still share the thread's session history.

Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) also receive thread-bound session operations directly on `ctx`. `ctx.cancel({ turnId? })` remains useful for a Stop button: it cancels without sending replacement input. `"accepted"` means the live thread session durably queued that request; a parked session consumes it as a no-op, and `"no_active_turn"` means the thread has no active session owner.
Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) also receive thread-bound session operations directly on `ctx`. `ctx.cancel({ turnId? })` remains useful for a Stop button: it cancels without sending replacement input. The returned `Delivery` identifies the queued command. An idle session accepts a no-op cancellation; an unowned thread throws `SessionNotActiveError`.

Message and interaction hooks expose every current-owner operation directly on `ctx`:

Expand Down Expand Up @@ -298,11 +298,11 @@ export default slackChannel({
});
```

Reset returns `{ status: "reset", previousSessionId }` when the thread had a session, or `{ status: "no_active_session" }` when it was already free. Both outcomes are successful. Returning `null` after reset consumes the triggering message as a command; return `{ auth }` instead to deliver that message as the first turn of the fresh session. Custom `onInteraction` handlers receive the same flat operations, which is useful for a New conversation button.
Reset returns a `Delivery` for the retiring session; `await delivery.result()` waits for retirement and address release. An already unowned thread throws `SessionNotActiveError`. Returning `null` after reset consumes the triggering message as a command; return `{ auth }` instead to deliver that message as the first turn of the fresh session. Custom `onInteraction` handlers receive the same flat operations, which is useful for a New conversation button.

### Other Events API callbacks

Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth, title })` zero, one, or many times to start turns. `title` sets the run title without changing the message sent to the model. Each call returns the resulting session.
Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth, title })` zero, one, or many times to start turns. `title` sets the run title without changing the message sent to the model. Each call returns a delivery receipt; use its `sessionId` to attach a session handle.

```ts title="agent/channels/slack.ts"
import { connectSlackCredentials } from "@vercel/connect/eve";
Expand Down
11 changes: 5 additions & 6 deletions docs/concepts/sessions-runs-and-streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,12 @@ POST to the session's cancel endpoint to stop the turn that is currently running

```bash
curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId>/cancel
# {"ok":true,"sessionId":"<sessionId>","status":"accepted"}
# {"ok":true,"sessionId":"<sessionId>","deliveryId":"delivery_cancel"}
```

`"accepted"` means the live session durably queued the request; cancellation completes asynchronously. The cancel arrives as a control delivery; confirm it on the stream as `turn.settled` with `outcome: "cancelled"` and `cause` naming that delivery. The session then accepts the next message normally. Each cancelled child reports its own boundary on its child-session stream. Cancelling also stops every working task, each reported as `task.ended` with `outcome: "cancelled"`, including while the session waits between turns. A live but already-parked session returns `"accepted"`; with no working tasks, cancellation is a no-op there. `"no_active_turn"` means the session or channel address is unknown or terminal. Both statuses are success, so clients can fire and forget. See the [eve channel](../channels/eve) for the full route contract.
The HTTP route returns `202` with the command's `sessionId` and `deliveryId`. Cancellation completes asynchronously. The cancel arrives as a control delivery; confirm it on the stream as `turn.settled` with `outcome: "cancelled"` and `cause` naming that delivery. Each cancelled child reports its own boundary on its child-session stream. An unguarded cancellation also stops working tasks, including between turns, with `task.ended` facts.

The HTTP route returns `202` for `"accepted"` and `200` for
`"no_active_turn"`. Only the accepted result includes `sessionId`.
A live idle session accepts a no-op cancellation. A guard for a different turn, including `turnId: null` for no observed turn, cannot cancel unrelated work. The cancellation delivery settles when applied or ignored. An unknown or terminal session returns `409 session_not_active`, not a separate `no_active_turn` result.

Custom channel routes request the same cancellation through
`from(address).cancel()` or `attachSession(sessionId).cancel()`. See
Expand All @@ -295,9 +294,9 @@ Compaction summarizes context without adding a user message. User-role instructi

Clear removes model-message history in place, including static and dynamic user-role instructions and recalled memory records, while preserving the session identity, system-role instructions, tools, skills, application-defined durable state, limits, and sandbox. It clears framework memory locks and replay bookkeeping but does not delete data from a provider's external store. It does not rerun instruction definitions or resolvers. Approvals, the session-limit prompt, and sign-ins the cleared history asked for are withdrawn: each settles its interaction. Requests relayed from tasks that keep running stay answerable. The clear is a context change with `kind: "clear"`.

Reset terminally retires the exact session ID. A reset ID never becomes a new session; create another session explicitly for a fresh conversation. Compact, clear, and reset return `"no_active_session"` when the target is already inactive.
Reset terminally retires the exact session ID. A reset ID never becomes a new session; create another session explicitly for a fresh conversation. All controls return delivery receipts when accepted. An inactive target throws `SessionNotActiveError` from a handle or returns `409 session_not_active` over HTTP.

A [stranded session](./execution-model-and-durability#stranded-sessions) cannot run commands. Reset still works: eve cancels the stranded workflow run and returns `"reset"`. Clear is refused with `409 session_stranded` (`SessionStrandedError` from a `Session` handle), because it would keep a session that cannot run. Compact returns `"no_active_session"` and cancel returns `"no_active_turn"`.
A [stranded session](./execution-model-and-durability#stranded-sessions) cannot run commands. Reset still works: eve retires the stranded workflow run and returns its delivery. Its settlement follows retirement and address release. Clear and compact cannot repair a stranded owner; use reset instead.

## Reconnect and rewind

Expand Down
33 changes: 31 additions & 2 deletions docs/concepts/upgrade-to-v27-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,13 +343,42 @@ are type-only exports. Obtain them from `new Client()` and its methods instead o
- Turn results expose `view`, `reply`, `data`, `sessionEnded`, and the shared request status.
- Match v27 facts in `t.event(...)`, `eventOrder(...)`, and `waitForEvent(...)`. Event matchers can
match a fact's `scope`.
- `turn.waitForToolCall(name, options)` waits for a settled call to a tool, and `toolCallsOf(events)`
derives tool calls from events. Eval tool calls always carry their `callId`.
- `turn.waitForToolCall(name, options)` waits for a settled call to a tool. Replace `toolCallsOf`
with `Object.values(turn.view.calls)`; names are `call.capability.name`, and settled results use
`call.outcome`. Use `callOutputSource(turn.view, call.callId)` from `eve/events` when a call shares
another call's output. Eval tool calls still carry their `callId`.
- The subagent matcher's `remoteUrl` is removed, and a subagent call's `status` is `pending` until
it settles, then its outcome (`completed`, `failed`, `rejected`, `interrupted`, `abandoned`).
- Interactions are `interactions`, `pendingInteractions`, and `requireInteraction(filter)`, sign-ins
included; match on `kind`.

## Public imports and authoring types

- Import fact and view types from `eve/events`, `SessionContext` and `HistoryMessage` from
`eve/context`, and assertion types and `ToolStub` from `eve/evals`. The client no longer
re-exports the event catalog's types. Named judge, mock-model, approval, and disabled-marker
types remain available.
- Connection authorization uses `principalType: "app" | "user"`; omitted getToken-only
ownership defaults to `"app"`. Replace `credentialOwner` and use `AuthorizationDefinition`
instead of `ConnectionAuthProvider`.
- All channels use `ChannelCors`. Use `allowHeaders`, `exposeHeaders`, and
`preflight: { statusCode }`; `origin` accepts a single string or an exact-origin list.
- History and sandbox types are eve-owned. Replace `RuntimeSandboxSessionFor<S>` with
`RuntimeSandboxSession<S>`; the eight sandbox method option types remain exported.
- Session handles read with `stream()`. `getEventStream`, `getLineStream`, and `getChildBinding`
are no longer public handle methods. Callback routing and forwarded initiator auth are
framework plumbing, not authored send options.
- Platform entries keep channel factories, default auth, continuation-token helpers, and authoring
types. Raw platform calls, verification, rendering, and wire constants are internal, except for
the documented Slack, Linear, and Chat SDK helpers. Built-in tool schemas remain on the tool
definitions; separate schema constants are no longer exported.
- `eve/setup` is no longer public. Scaffold integrations move to `eve/internal/scaffold`.
`eve/internal/*` and `eve/experimental/*` have no compatibility promise.
- Mount `eve/self-modification/local` or `/remote` explicitly. The bare entry and `/config` are
removed. Preserve authored changes before removing the retired
`agent/subagents/self-modification/` layout; setup reports the replacement mount rather than
deleting those files.

## MCP and `eve invoke`

- **MCP channel.** `input_required` remains the wire spelling for `awaiting-input`; sign-ins now
Expand Down
2 changes: 1 addition & 1 deletion docs/connections/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export default defineMcpClientConnection({
url: "https://mcp.example.com/mcp",
description: "Social publishing: draft, schedule, and manage posts.",
auth: {
credentialOwner: "app",
principalType: "app",
getToken: async () => ({ token: process.env.SOCIAL_API_KEY! }),
},
approval: ({ toolName, toolInput }) => {
Expand Down
Loading
Loading