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

- Repository: vercel/eve
- GitHub: https://github.com/vercel/eve
- Human docs: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f
- Complete Markdown: https://www.grok-wiki.com/public/docs/vercel-eve-759e1d74a10f/llms-full.txt

## Source Files

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

---

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

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

## Session, turn, and step nesting

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

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

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

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

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

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

## Two-workflow architecture

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

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

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

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

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

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

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

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

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

## Durable checkpoints and wire migrations

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

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

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

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

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

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

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

## Crash resume semantics

Durability is on by default — no configuration required.

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

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

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

## Driver action contract

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

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

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

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

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

## Parked work

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

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

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

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

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

### OAuth and connection authorization

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

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

### Subagents and remote agents

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

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

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

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

## `continuationToken` and message delivery

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

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

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

### Deliver and resume-or-start

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

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

### Coalescing and ordering constraints

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

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

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

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

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

## Run modes and terminal events

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

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

## Accessing session data from authored code

Two surfaces expose session data without touching workflow primitives:

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

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

## Related pages

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