An Agent Definition names a server-side actor and the Agent Driver that runs it. The driver can call a model, launch a coding provider, or execute application-owned logic through the same Agent Invocation boundary.
This tutorial starts with application-owned logic. The deterministic first proof needs no key, network request, or model billing. After the invocation works, you can replace the driver with an AI SDK model without changing the HTTP route.
pnpm. The first half of the tutorial is
offline and free to run.Create the project
Create an empty directory and install ViteHub with the H3 server used in this tutorial.
mkdir vitehub-agent-start
cd vitehub-agent-start
pnpm init
pnpm pkg set type=module
pnpm add vite-hub h3 vite
Create this small file tree:
vitehub-agent-start/
├── server/
│ └── agents/
│ └── greeting.ts
├── src/
│ └── server.ts
└── vite.config.ts
Configure the server build
Register vitehub() so the framework discovers the Agent Definition and owns
the server integration. The route imports the Definition directly for the
smallest invocation proof.
import { resolve } from "node:path"
import { defineConfig } from "vite"
import { vitehub } from "vite-hub"
export default defineConfig({
root: import.meta.dirname,
appType: "custom",
build: {
outDir: "dist",
rollupOptions: {
input: resolve(import.meta.dirname, "src/server.ts"),
output: { entryFileNames: "server.js" },
},
ssr: true,
},
plugins: [vitehub({
preset: "node",
agent: true,
blob: false,
env: false,
})],
ssr: {
external: ["vite-hub/agent"],
},
})
The Vite build compiles one Node.js server entry and leaves the Agent package as a Node.js runtime dependency. H3 owns HTTP behavior, while ViteHub owns the Agent Definition and Agent Invocation contract.
Define a deterministic Agent
Create a greeting Agent with a driver.run function. Application-owned
drivers are useful for deterministic orchestration, migrations, and testing
the Agent boundary before you introduce a provider.
import { defineAgent } from "vite-hub/agent"
export default defineAgent({
description: "Returns a deterministic greeting for the first tutorial.",
runtime: false,
driver: {
run({ prompt }) {
const name = typeof prompt === "string" ? prompt : "friend"
return {
text: `Hello, ${name}. This result came from an Agent Invocation.`,
}
},
},
})
The route imports this Definition directly and needs the completed result for
its HTTP response, so runtime: false opts out of the hosted Workflow default.
The file location still keeps the Agent visible and reviewable before any
invocation starts.
Run one Agent Invocation
Create an H3 route that passes trusted host context separately from invocation
input. memo scopes lazily created values to this invocation, while
waitUntil receives background work that may finish after the main result.
First create the invocation-scoped memoizer. A factory runs at most once for a key, and each request gets a fresh cache.
export function createMemo() {
const values = new Map<string, unknown>()
return <T>(key: string, create: () => T): T => {
if (!values.has(key)) values.set(key, create())
return values.get(key) as T
}
}
import { createServer } from "node:http"
import { H3, readBody } from "h3"
import { toNodeHandler } from "h3/node"
import { runAgent } from "vite-hub/agent"
import greeting from "../server/agents/greeting"
import { createMemo } from "./memo"
const app = new H3().post("/greet", async (event) => {
const body = await readBody<{ name?: string }>(event) || {}
return await runAgent(greeting, {
memo: createMemo(),
runtime: "vite",
waitUntil: task => { void task.catch(error => console.error(error)) },
}, {
prompt: body.name?.trim() || "friend",
})
})
const port = Number(process.env.PORT || 5173)
createServer(toNodeHandler(app)).listen(port, () => {
console.log(`ViteHub Agents tutorial listening on http://localhost:${port}`)
})
Production integrations forward waitUntil to the host when that host exposes
a background-lifetime primitive. This standalone Node.js example handles any
background rejection locally.
Run the proof
Build and start the server.
pnpm vite build
node dist/server.js
Invoke the Agent from another terminal.
curl -X POST http://localhost:5173/greet \
-H 'content-type: application/json' \
-d '{"name":"Ada"}'
The deterministic Agent returns an inspectable result:
{"text":"Hello, Ada. This result came from an Agent Invocation."}
You now have a real Agent Definition and a real Agent Invocation. You have not paid for a model call or hidden the runtime context behind framework globals.
Upgrade the driver to a model
The model-backed path materializes model strings through AI Gateway. An Agent Definition can also return { id, apiKey } for invocation-aware credentials or accept any compatible concrete AI SDK model.
Add the Gateway credential to your local environment.
AI_GATEWAY_API_KEY=your_key_here
AI_GATEWAY_API_KEY on the
server before you run it.Replace the deterministic Agent Driver with a model and durable instructions.
import { defineAgent } from "vite-hub/agent"
export default defineAgent({
description: "Greets a person with one warm, concise sentence.",
driver: {
instructions: "Greet the named person in one warm, concise sentence.",
model: "openai/gpt-5.1-mini",
},
})
Rebuild the server, then restart it with Node.js loading the local environment file explicitly.
pnpm vite build
node --env-file=.env dist/server.js
Repeat the request. The response still exposes a text field, but the wording
now comes from the selected model.
The route does not change because runAgent() owns the invocation boundary.
The Agent Definition remains the place to review the driver, instructions, and
future Capabilities.
Add abilities only when needed
Capabilities give an Agent Driver explicit abilities such as Workspace inspection, KV access, Blob storage, shell execution, or MCP tools. Model, provider, and application-owned Drivers receive the selected tool set through their own Driver boundary. Start with the smallest Capability set that the product needs; the first Agent does not need any.
Continue from here
- Read Agent Definitions for drivers, hooks, and runtime options.
- Read Invocations for trusted context, streaming, and failure handling.
- Read Capabilities before exposing tools or data to an Agent Driver.
- Build your first Server Primitive when application code needs infrastructure without an Agent.