# Agent Drivers

An Agent Driver decides how one invocation runs. Choose the execution method that matches the work.

| Choose | Use it when |
| --- | --- |
| Model-backed | ViteHub runs a model and its Capability-contributed tool loop. |
| Provider-backed | Codex or Claude Code runs the coding-agent loop, tools, approvals, and session. |
| Custom run | Application code runs the entire operation. |
| Ask | A fast decision model answers a fixed set of typed questions. |

Built-in `"codex"` and `"claude-code"` values are provider-backed. Application-supplied Drivers use exactly one of `{ model }`, `{ run }`, or `{ ask }`.

## Use a model-backed Driver

Model-backed execution fits support answers, classification, extraction, structured output, and bounded tool use.

```ts [server/agents/support.ts]
import { defineAgent } from 'vite-hub/agent'

export default defineAgent({
  driver: {
    model: 'openai/gpt-5.1-mini',
    instructions: 'Answer support requests from inspected evidence.',
    execution: {
      callSettings: { temperature: 0.2 },
      stepLimit: 8,
    },
  },
})
```

Model strings run through AI Gateway. ViteHub discovers `AI_GATEWAY_API_KEY` from the process or Cloudflare Server Env. Supply an explicit descriptor when the Definition owns the credential:

```ts [server/agents/support.ts]
import { defineAgent } from 'vite-hub/agent'

const apiKey = process.env.SUPPORT_AI_GATEWAY_API_KEY
if (!apiKey) throw new Error('SUPPORT_AI_GATEWAY_API_KEY is required')

export default defineAgent({
  driver: {
    model: { id: 'zai/glm-5v-turbo', apiKey },
  },
})
```

The `model` value may also be a compatible AI SDK model or an invocation-time callback. Keep authorization in Access or Capability policy; use model callbacks and instrumentation for routing and call settings.

### Model options

| Option | Purpose |
| --- | --- |
| `model` | Required model id, `{ id, apiKey }`, AI SDK model, or callback. |
| `instructions` | String, string array, or callback parts. Defaults to colocated instructions when available. |
| `maxRetries` | Common model retry count. Do not also set `execution.callSettings.maxRetries`. |
| `execution.callSettings` | Provider and AI SDK call settings. |
| `execution.stepLimit` | Maximum model tool-loop steps; defaults to `20`. |
| `execution.instrumentation` | Invocation-scoped model wrapping or call-setting overrides. |
| `execution.workspaceFallback` | Controls synthesis from Workspace evidence when a run produced tool results but no text. |

## Use a provider-backed Driver

The built-in Drivers reuse T3 Code's normalized Codex and Claude Code runtime while ViteHub owns Agent Definitions, Capabilities, Workspaces, Invocations, and public lifecycle events.

Install only the provider packages selected by your Agent Definitions:

```sh
pnpm add @openai/codex@0.149.1
pnpm add @anthropic-ai/claude-agent-sdk@0.3.246
```

```ts [server/agents/review/agent.ts]
import { defineAgent } from 'vite-hub/agent'
import { loadServerEnv } from '#vitehub/env/server'

export default defineAgent({
  driver: {
    credentialProfile: 'review',
    credentials: async ({ abortSignal }) => (await loadServerEnv(undefined, { signal: abortSignal })).codexAuthJson,
    kind: 'codex',
    instructions: 'Review the exact pull request head before changing code.',
    model: 'gpt-5.5',
    permissions: 'ask',
    reasoningEffort: 'high',
    reasoningSummary: 'detailed',
  },
  workspace: { mode: 'write' },
})
```

`credentials` accepts the contents of Codex `auth.json`, a sealed Server Env value, or an invocation-time resolver. This lets a Kubernetes secret or an [Env provider](https://vitehub.dev/docs/env/server-api#read-external-env-storage) supply auth without startup file plumbing. ViteHub resolves it for each invocation, validates the JSON object, and projects it as a `0600` file inside a `0700` Codex Home. Provisioned credentials require a POSIX host; ViteHub rejects them on Windows because these file modes cannot guarantee owner-only access there. The value never enters the provider environment or inspection output.

A named `credentialProfile` shares one writable Home between Drivers in the process and stores it at `.vitehub/data/codex/<credentialProfile>`. ViteHub serializes Codex runtime access to the profile, so one Codex process owns that Home at a time. Codex can refresh `auth.json` there. ViteHub fingerprints the last external seed, preserves Codex's refreshed file while that seed is unchanged, and replaces the file on the next invocation when the resolver returns a rotated value.

The directory is a complete Codex Home, not a credentials-only store; treat its auth, configuration, session state, and logs as sensitive. A profile supports one ViteHub process writing its volume. In Kubernetes, give each replica its own persistent volume and profile state rather than mounting one profile through a shared multi-writer volume. ViteHub does not write refreshed credentials back to the external Env provider. Without `credentialProfile`, each invocation receives an isolated temporary Home that ViteHub removes after the Codex runtime stops.

Provider Drivers require a local Node.js host with credentials available to the process. Codex credentials without an explicit resolver must be available to that process. ViteHub resolves an installed `@openai/codex` or `@anthropic-ai/claude-agent-sdk` package without requiring deployment-specific copy scripts. Production self-hosted Node builds on macOS and Linux copy only the build host's native optional package into `.output/server/node_modules`, including the Linux libc variant, so build on the same host type used for deployment. If Codex is absent, ViteHub falls back to `codex` on the host `PATH`. Claude requires the Agent SDK, while a missing native SDK package at runtime retains T3's host `claude` command fallback. ViteHub does not download provider packages during build or startup. Provider Workspaces also require a POSIX host. Each invocation receives a temporary working directory, optional Workspace files, Codex instructions or a literal Claude prompt file, and Capability tools through a private loopback MCP server. Successful write-mode runs commit through Workspace rules; failed and cancelled runs do not write back.

Provider runtime cursors resume a thread while the Agent Definition process remains active. Set `sessionStorePath` to persist those opaque cursors in a local SQLite file across process restarts. Codex credentials supplied through `credentials` require a named `credentialProfile` before session persistence can be enabled because an invocation-private Codex Home is removed after each run. Relative paths resolve from the process working directory. Dedicate each file to one provider Agent Definition on one persistent process host; the store does not coordinate concurrent ownership of one thread across workers. Chat-backed cursors remain partitioned by origin, invoker, and resolved Chat Session, so a new session cannot inherit provider context from an earlier one.

Resolve `env` per invocation when a provider needs short-lived credentials. Use `launch` when the provider process must start through an application-owned wrapper such as a remote execution CLI:

```ts [server/agents/review/agent.ts]
import { defineAgent } from 'vite-hub/agent'

export default defineAgent({
  driver: {
    env: async ({ abortSignal }) => ({
      SANDBOX_TOKEN: await issueSandboxToken({ signal: abortSignal }),
    }),
    kind: 'codex',
    launch: ({ command }) => ({
      args: ['exec', '--', command],
      command: 'sandboxctl',
    }),
    permissions: 'allow-all',
  },
})
```

ViteHub resolves `env` once, then gives `launch` the selected environment, executable to start in `command`, original provider executable in `providerCommand`, temporary working directory, and abort signal. Use `command` to construct wrapper arguments and `providerCommand` to select a provider-specific runner. They are equal during invocations. The wrapper receives its configured arguments followed by the provider runtime arguments. ViteHub writes an owner-only temporary launcher outside the Workspace and removes it after the provider runtime stops. Launch wrappers require a POSIX host. They change process startup only; authorization, remote isolation, and wrapper credentials remain application-owned.

Agent inspection reports whether `env` and `launch` are static or dynamic without resolving either value.

Set `requirements` to the commands that the provider's own shell needs, for example `['git', 'gh', 'unzip', 'apply_patch']`. `agent.status()` checks each command with `command -v` where the Driver runs, or `where.exe` for a local Windows Driver. With `launch`, requirements need a resolver. Status inspection resolves it once with `command: 'sh'`. Each provider probe checks the commands in that same shell before it starts the provider, so checking requirements does not allocate another runner. A status probe may execute the resolved launcher more than once for separate provider checks. If the launcher does not report command-check results, requirement readiness remains unknown. Missing commands make the status `unavailable` and appear in `status.missingCommands` and `status.reason`. Invocations do not repeat the check.

Resolvers that route by the provider executable must use `providerCommand` before enabling `requirements`. During requirement inspection, `command` is `sh` and `providerCommand` still identifies the provider. An application-owned runner selector can use both fields:

```ts
launch: ({ command, providerCommand }) => ({
  args: ['exec', '--', command],
  command: selectRunnerForProvider(providerCommand),
})
```

### Route model requests through a gateway

Set `gateway` to send the provider's model requests to an LLM proxy or gateway. Import a preset from `vite-hub/agent/gateways`. Like a Nuxt module, each preset reads its URL and key from the environment, so the code names only the gateway:

```ts [server/agents/review/agent.ts]
import { defineAgent } from 'vite-hub/agent'
import { cliproxy, cloudflareAccess } from 'vite-hub/agent/gateways'

export default defineAgent({
  driver: {
    kind: 'codex',
    model: 'gpt-5.5',
    // Reads CLIPROXY_URL, CLIPROXY_API_KEY, CF_ACCESS_CLIENT_ID, and CF_ACCESS_CLIENT_SECRET.
    gateway: cliproxy({ headers: cloudflareAccess() }),
  },
})
```

```sh [.env]
CLIPROXY_URL=https://proxy.example.com
CLIPROXY_API_KEY=...
CF_ACCESS_CLIENT_ID=...
CF_ACCESS_CLIENT_SECRET=...
```

Pass a value to override its variable, for example `cliproxy({ url: 'https://proxy.example.com', apiKey: () => useServerEnv().proxyKey })`.

Each preset knows the base URL that each Driver expects, so one gateway definition works for Codex and Claude Code:

| Preset | Codex base URL | Claude Code base URL | Variables |
| --- | --- | --- | --- |
| `cliproxy()` | `<url>/v1` | `<url>` | `CLIPROXY_URL`, `CLIPROXY_API_KEY` |
| `litellm()` | `<url>/v1` | `<url>` | `LITELLM_URL`, `LITELLM_API_KEY` |
| `ollama()` | `<url>/v1` | `<url>` | `OLLAMA_URL` (default `http://localhost:11434`), `OLLAMA_API_KEY` (default `ollama`) |
| `openrouter()` | `https://openrouter.ai/api/v1` | `https://openrouter.ai/api` | `OPENROUTER_API_KEY` |
| `vercel()` | `https://ai-gateway.vercel.sh/codex/v1` | `https://ai-gateway.vercel.sh/claude-code` | `AI_GATEWAY_API_KEY`, then `VERCEL_OIDC_TOKEN` |
| `openai()` | `https://api.openai.com/v1` | | `OPENAI_API_KEY` |
| `anthropic()` | | `https://api.anthropic.com` | `ANTHROPIC_API_KEY` |

`cloudflareAccess()` returns the `CF-Access-Client-Id` and `CF-Access-Client-Secret` headers for a gateway behind Cloudflare Access. It reads `CF_ACCESS_CLIENT_ID` and `CF_ACCESS_CLIENT_SECRET`, or takes `{ clientId, clientSecret }`.

Each value first reads its canonical name, `VITEHUB_` and the Server Env path in upper snake case, then the vendor name in the table. `cliproxy()` reads `VITEHUB_CLIPROXY_API_KEY`, then `CLIPROXY_API_KEY`, and `cloudflareAccess()` reads `VITEHUB_CLOUDFLARE_ACCESS_CLIENT_ID`, then `CF_ACCESS_CLIENT_ID`. See [Server Env variable names](https://vitehub.dev/docs/env/configure).

ViteHub finds the presets in Agent files and declares their variables in Server Env as `env.server.<preset>.<field>`, for example `env.server.cliproxy.apiKey`. The Console shows them, `useServerEnv()` types them, and an [Env provider](https://vitehub.dev/docs/env/server-api#read-external-env-storage) can supply them. Your own declaration of a field wins. The values are optional: a server that hosts Agents without the gateway still starts. Without `hubEnv()`, presets read the host variables directly.

`url` is the gateway origin. A trailing `/v1` is removed. Every preset accepts `apiKey` and `headers`. `apiKey` and each header value accept a string, a sealed Server Env value, or an invocation-time resolver that may return `undefined`. ViteHub reads values for each invocation. A missing URL, key, or header value fails the invocation with `AGENT_R0980`, `AGENT_R0977`, or `AGENT_R0978` and names the variable to set. A preset that does not serve the selected Driver fails at definition time with `AGENT_R0976`.

Use `defineGateway()` for an endpoint without a preset:

```ts
import { defineGateway } from 'vite-hub/agent/gateways'

export const gateway = defineGateway({
  name: 'internal',
  baseURL: { 'codex': 'https://llm.example.com/openai/v1', 'claude-code': 'https://llm.example.com/anthropic' },
  apiKeyEnv: ['INTERNAL_LLM_KEY'],
  auth: 'x-api-key',
})
```

`auth` selects how the gateway receives the key. `'bearer'`, the default, sends `Authorization: Bearer <key>`. `'x-api-key'` sends `x-api-key: <key>`.

`apiKeyEnv` reads host variables, not Server Env. Use `apiKey: () => useServerEnv().internal.key` when the key is declared in Server Env. A `baseURL` entry can also be a resolver, so the URL can come from Server Env for each invocation.

ViteHub converts the gateway to each provider's own configuration:

- Codex receives a generated `vitehub` model provider through `-c` overrides: `base_url`, `wire_api = "responses"`, `env_key`, and `env_http_headers`. The key and the header values go in the provider environment as `VITEHUB_GATEWAY_API_KEY` and `VITEHUB_GATEWAY_HEADER_<n>`. The launch arguments contain only variable names. The gateway overrides come after `providerSettings.launchArgs`, so they take precedence.
- Claude Code receives `ANTHROPIC_BASE_URL`, the key in `ANTHROPIC_AUTH_TOKEN` (bearer) or `ANTHROPIC_API_KEY` (x-api-key) with the other variable set to empty, and `ANTHROPIC_CUSTOM_HEADERS`.

The gateway owns these variables. Setting one of them, or `T3CODE_CODEX_LAUNCH_ARGS` for Codex, in `driver.env` fails with `AGENT_R0979`. A gateway replaces `credentials` and `credentialProfile`, including values inherited through `defineAgent({ extends })`. A `launch` wrapper receives the gateway variable names in `requiredEnvironment`, so SSH runners forward them. Agent inspection reports the gateway name and `executionAuthority.credentials: "provisioned"`. `agent.status()` does not cache results for an Agent with a gateway.

ViteHub does not forward ambient variables such as `OPENAI_API_KEY`, `ANTHROPIC_BASE_URL`, or `CLIPROXY_API_KEY` to the provider. Use a gateway or `driver.env` to select them.

A gateway changes where requests go, not their protocol. The endpoint must accept OpenAI Responses requests from Codex and Anthropic Messages requests from Claude Code, and `model` must be a model ID that the gateway accepts. The key and header values are in the provider process environment, so commands that the provider runs can read them. Treat a gateway key as available to the Agent. Prefer API keys: routing a Claude subscription sign-in through a proxy is not permitted by [Anthropic's terms](https://code.claude.com/docs/en/legal-and-compliance).

### Run in an existing directory

By default, each invocation receives a new temporary working directory. Set `cwd` when the provider must run in a directory that the application prepares, such as a disposable local checkout that is also the Workspace's local store root:

```ts [server/agents/review/agent.ts]
import { defineAgent } from 'vite-hub/agent'

export default defineAgent({
  driver: {
    cwd: '/srv/review/checkout',
    kind: 'codex',
    permissions: 'allow-edits',
  },
})
```

`cwd` accepts an absolute or relative path, or an invocation-time resolver. Relative paths resolve from the process working directory. The resolved value must be an existing directory; otherwise the invocation fails with `AGENT_R0939` before the provider starts. A `launch` resolver receives this directory as `cwd`.

The directory is the working copy. ViteHub still materializes Workspace Sources, but it does not start a Workspace session. It does not copy Workspace files into the directory, create a Git baseline, compute a diff, write changes back, or remove the directory. Generated instruction and Skill files are restored after the provider stops. When the directory is a Git repository root, Git ignores the generated files while the provider runs, so provider commits do not include them. Title and progress summary runs ignore `cwd` and use a temporary directory. The application owns the directory's contents, cleanup, and isolation between concurrent invocations. Agent inspection reports whether `cwd` is static or dynamic without resolving it.

### Provision the project toolchain

Set `toolchain` when the provider works on a JavaScript project that pins its own Node.js and package manager. It uses the same declaration as [`box.toolchain`](https://vitehub.dev/docs/agents/boxes#provision-nodejs-and-the-package-manager):

```ts [server/agents/review/agent.ts]
import { defineAgent } from 'vite-hub/agent'

export default defineAgent({
  driver: {
    kind: 'codex',
    permissions: 'allow-all',
    toolchain: 'project',
  },
})
```

After ViteHub prepares the pull request checkout, the Workspace, or `cwd`, it reads the pins from that directory, provisions them on this host under `$XDG_CACHE_HOME/vitehub/toolchains`, and checks `node -v` and the package manager version. The toolchain `bin` directories come before the host `PATH` for the provider process, every command it starts, and Workspace commands. With a pull request mounted below the Workspace root, ViteHub reads the pins from that mount. A launcher from `launch` receives the same `PATH` in its environment, so it must run on this host. Provider CLIs that start through `#!/usr/bin/env node`, such as the npm Codex package, also run on the project's Node.js. Title and progress summary runs do not provision a toolchain.

`status()` does not report `node`, `npm`, `npx`, `pnpm`, `pnpx`, `yarn`, or `yarnpkg` as missing when `toolchain` is set, because the toolchain exists only after an invocation prepares its checkout. With `box`, declare `box.toolchain` instead; the Agent rejects `driver.toolchain` with `AGENT_R0972`.

Threads resume with the provider's opaque cursor. ViteHub normalizes assistant text, reasoning, native and Capability tool activity, approvals, provider questions, usage, warnings, errors, and terminal state into Agent Invocation events.

| Option | Purpose |
| --- | --- |
| `kind` | Required tagged provider name: `"codex"` or `"claude-code"`. |
| `credentialProfile` | Codex-only stable credential Home name. Requires `credentials`; persists under `.vitehub/data/codex/<credentialProfile>`. |
| `credentials` | Codex-only `auth.json` string, sealed Server Env value, or invocation-time resolver. Inspection reports that a source is configured without resolving or checking the value. |
| `reasoningEffort` | Codex-only non-empty model-advertised effort override, such as `"low"`, `"high"`, or `"xhigh"`. Requires an explicit `model`. |
| `reasoningSummary` | Codex-only `"auto"`, `"concise"`, `"detailed"`, or `"none"` override. Requires an explicit `model`. |
| `model` | Optional provider model id. |
| `env` | Explicit environment values or an invocation-time resolver. ViteHub resolves it once and otherwise inherits only standard host paths, locale, and user-directory variables, not arbitrary application secrets. |
| `execution.attachments.maxBytes` | Optional positive per-invocation image attachment budget; defaults to 25 MiB. Inline and application-resolved lazy images share the budget. |
| `instructions` | Invocation-scoped instructions composed with colocated instructions. |
| `launch` | Provider command wrapper or invocation-time resolver. Receives the provider executable, working directory, selected environment, and abort signal. |
| `cwd` | Optional existing directory or invocation-time resolver. The provider runs there without a Workspace session, write-back, or removal. See [Run in an existing directory](#run-in-an-existing-directory). |
| `permissions` | `"ask"`, `"allow-edits"`, `"allow-edits-unattended"`, or `"allow-all"`; defaults to `"ask"`. `"allow-edits-unattended"` keeps the provider edit mode and denies native permission escalation without prompting. Host-bound MCP tools keep their separate authorization. Set `"allow-all"` explicitly to run provider actions without approval. |
| `providerSettings` | Advanced settings passed to the embedded provider runtime. Explicit settings override the installed Codex executable fallback. |
| `requirements` | Command names that `status()` checks where the Driver runs. Missing commands are reported in `missingCommands`. |
| `sessionStorePath` | Optional SQLite file for provider session cursors. Enables thread continuation after a process restart on the same persistent host volume. |
| `toolchain` | `"project"` or `{ node, packageManager, fallbackNode }`. Provisions project-pinned Node.js and package manager for the provider and its commands. See [Provision the project toolchain](#provision-the-project-toolchain). |
| `output` | Optional structured Agent output contract. |
| `capacity` | Optional process-local static or adaptive concurrency and queue limits. |

Provider Drivers do not accept model-specific Provider Tool contributions, Cloudflare Agents, or Deno. Provider Workspaces are also unsupported on Windows. These boundaries fail explicitly. Workspace-scoped Skills and ordinary Capability tools are supported.

## Run a provider Driver in a Box

Set `box` on the Agent Definition to start the provider inside a [Box](https://vitehub.dev/docs/agents/boxes). The Box supplies the working tree, private Home, environment, and boot checks for each invocation:

```ts [server/agents/review/agent.ts]
import { defineAgent } from 'vite-hub/agent'

export default defineAgent({
  box: {
    runtime: 'trusted-host',
    requires: ['git'],
  },
  driver: { kind: 'codex', permissions: 'allow-all' },
})
```

The provider runtime stays in the ViteHub process. Only the provider command runs in the Box. ViteHub adds the provider command to the Box requirements and writes the Driver instructions and Skills to the Box Home. `driver.env` values reach the provider in the Box. With `box`, the Driver rejects `launch`, `credentials`, and `credentialProfile`, and the Agent rejects `workspace`. Put provider credentials in the Box instead. A Box session lasts one invocation, so the provider does not resume a thread from an earlier invocation.

### Cloudflare Workers

Provider Drivers start local Codex or Claude Code processes, so they cannot run in a Cloudflare Worker. Worker builds exclude the provider Driver runtime and `@t3tools/provider-runtime`: `@vite-hub/agent` resolves its provider module to a Worker module through the `workerd` and `worker` package conditions. Apps that use only `{ model }` or `{ run }` Drivers do not bundle Node-only provider code.

A Worker build fails with `AGENT_B0019` when a server module selects a provider Driver with `driver: 'codex'`, `driver: 'claude-code'`, `{ kind: 'codex' }`, `codexDriver()`, `claudeCodeDriver()`, or a first-party preset that uses Codex. The same check covers a provider Driver passed to the `title()` or `progressSummary()` Capability, including imported aliases and namespace calls. ViteHub checks parsed module syntax, so it ignores comments, strings, regular expressions, type-only imports, and unrelated objects. Computed or indirect selections are not resolved and fail at invocation time with `AGENT_R0928`. Deploy Agents that use provider Drivers to a Node.js host.

## Adaptive process capacity

Self-hosted Node applications can admit new work according to current machine pressure instead of relying on a fixed schedule. Keep one shared capacity object in an application module and reuse it across the Agent Definitions that compete for the same host resources:

```ts [server/agent-capacity.ts]
import { createProcessAgentCapacity } from 'vite-hub/agent/runtime/process'

export const agentCapacity = createProcessAgentCapacity({
  concurrency: 6,
  queue: { maxPending: 100, timeout: 30 * 60_000 },
})
```

`concurrency` is always the hard maximum. The adapter lowers only new admissions when CPU or memory is under pressure; active invocations continue, queued invocations remain FIFO, and admission resumes after recovery. Linux uses cgroup v2 limits, memory events, and pressure stall information when available. Other hosts fall back to Node's available-memory and parallelism signals. Failed samples or samples exceeding `sampleTimeoutMs` (one second by default) use `fallbackConcurrency`, which defaults to one. `vitehub agent info` reports the current admitted limit, hard maximum, queue depth, and sampling reason.

## Use a custom run Driver

Use `driver.run` when application code owns the result and no model loop is needed.

```ts [server/agents/router.ts]
import { defineAgent } from 'vite-hub/agent'

export default defineAgent({
  driver: {
    run({ input, invoker }) {
      return { text: `Accepted ${invoker.id}: ${String(input.prompt ?? '')}` }
    },
  },
})
```

The callback receives prepared input, messages, tools, Workspace access, invocation context, and the resolved Actor as both `actor` and `invoker`. A custom run callback may call a model internally, but ViteHub treats that execution and usage as application-owned behavior.

ViteHub cannot stop a custom run callback. When a user cancels the Invocation, `input.abortSignal` aborts, and the cancel result reports `notEnforcedBy: 'run'`. Pass the signal to your own I/O, or throw `input.abortSignal.reason`, to stop early. Model-backed and provider-backed Drivers stop on cancel. See [Cancel an invocation](https://vitehub.dev/docs/agents/invocations#cancel-an-invocation).

Read [Instructions](https://vitehub.dev/docs/agents/instructions) for model-facing behavior and [Workspace context](https://vitehub.dev/docs/agents/workspace-context) for files and writeback.

## Use an ask Driver

Use `driver.ask` when the Agent only has to answer a few typed questions, such as a label or a score. The Driver sends questions to [TypeSafe Jev](https://typesafe.ai) through the `advocaat` client in deterministic sequential batches when needed to stay within the provider request budget. For each question, the serialized shared state plus that question and request overhead must fit below 30,000 bytes; exceeding the combined limit fails the Invocation. An empty question map skips Jev. It does not run a chat model, tools, or instructions.

Install the client. It is an optional peer dependency, so Agents without `driver.ask` bundle without it:

```sh
pnpm add advocaat
```

Declare the `typesafe` Server Env group with `typesafeEnv()`. The Driver always reads this group name:

```ts [vite.config.ts]
import { defineConfig } from 'vite'
import { vitehub } from 'vite-hub'
import { typesafeEnv } from 'vite-hub/env'

export default defineConfig({
  env: {
    server: {
      typesafe: typesafeEnv(),
    },
  },
  plugins: [vitehub()],
})
```

Build the questions with `ask`:

```ts [server/agents/labeller.ts]
import { ask, defineAgent } from 'vite-hub/agent'

export default defineAgent({
  driver: {
    ask: {
      label: ask.choice('Which Gmail label fits this email?', {
        invoice: 'Bills, receipts, and payment requests.',
        none: 'No label clearly fits.',
      }),
      urgent: ask.if('Does the email need action today?'),
    },
  },
})
```

The answers are the Invocation output, and their type comes from the questions. The standalone `runAgent` overload returns a tuple, so destructure it before reading the result:

```ts
const [error, answers] = await runAgent(labeller, { prompt: 'Invoice 42' })
if (error) throw error
answers.label.choice // 'invoice' | 'none'
answers.urgent // boolean
```

No output schema or parsing is necessary.

Jev reads the Invocation `data` when a caller sets it, else the prompt text, else the latest user message. Pass a function to build the questions for each Invocation. It receives the same context as `driver.run`:

```ts [server/agents/labeller.ts]
import { useServerEnv } from '#vitehub/env/server'
import { ask, defineAgent } from 'vite-hub/agent'

export default defineAgent({
  driver: {
    ask: () => {
      const labels = useServerEnv().labeller.labels.split(',')
      return {
        label: ask.choice('Which label fits?', Object.fromEntries(labels.map(label => [label, null]))),
      }
    },
  },
})
```

| Builder | Answer |
| --- | --- |
| `ask.choice(instructions, criteria)` | `{ choice, confidence, probabilities }`. `criteria` maps labels to descriptions, or is a list of 2 to 255 labels. |
| `ask.switch(instructions, criteria)` | The selected label only. |
| `ask.score(instructions, levels)` | `{ score, ratio, confidence, legend, probabilities }` for 2 to 10 ordered levels. |
| `ask.chance(instructions, criteria?)` | `{ chance }`, the probability of yes. |
| `ask.if(instructions, { threshold? })` | `true` when the probability of yes is above `threshold`. The threshold must be between `0` and `1` and defaults to `0.5`. |

State, instructions, and criteria accept JSON values. Root numbers and booleans become text for the `advocaat` Entry contract. Numbers and booleans inside objects or arrays stay native. Score legends retain the original level descriptions.

`driver.ask` accepts only `ask` and `capacity`. Missing `advocaat`, a missing `typesafe` group, and a missing TypeSafe API key fail the Invocation with a diagnostic. The Console and `vitehub agent info` show the Driver kind as `ask`. When an ask Driver Agent uses [`llmGate()`](https://vitehub.dev/docs/agents/capabilities/llm-gate) or [`llmRoute()`](https://vitehub.dev/docs/agents/capabilities/llm-route) without a `model`, the decision also uses Jev.

### Provider exit evidence

A `launch` resolver can return `onExit({ cwd, abortSignal })` with its command. ViteHub calls this host callback once after the provider and Workspace commands stop, before it restores generated files or deletes the temporary working directory. Auxiliary runs, such as title generation, do not call it. Use it to read the final checkout HEAD and persist evidence in host-owned state. The callback also runs after a failed or cancelled turn when shutdown completes. Cancellation can return before this deferred cleanup finishes. Its signal has a separate teardown deadline; stop all I/O when it aborts. Callback errors fail cleanup without preventing directory removal.

The callback is skipped when provider shutdown fails or exceeds the cleanup deadline, and during provider inspection. Missing evidence must remain unknown. This callback does not verify model claims, grant push authority, or make closure state durable across host crashes. Validate the checkout in host code and persist the result before returning when durability is required.
