Durable agents with Vercel Workflow
Long-running assistants and chat backends often need durable execution: if a function restarts, the same logical run should resume, tool calls should retry safely, and each step should be observable. @github-tools/sdk supports this through the Vercel Workflow SDK and the @github-tools/sdk/workflow entry point.
Integrate a durable GitHub assistant
What “durable” means here
"use workflow"— Your orchestration function is a workflow: the platform can pause, resume, and replay it across failures and deploys."use step"— Each GitHub tool implementation runs inside a named, module-level step. Every tool call is a durable step with retries and full Node.js access in the workflow runtime.createDurableGithubAgent— Wraps the same GitHub tools in aWorkflowAgentfrom@ai-sdk/workflowso LLM turns and tool invocations participate in that durable model (not just rawgenerateTextin a plain serverless handler).
Together, this is the recommended pattern when you build Nuxt, Next.js, or other apps that expose a chat or agent API and must not lose progress on timeout or cold start.
Install optional workflow dependencies
Durable agents are optional. Add them only when you use @github-tools/sdk/workflow:
pnpm add workflow @ai-sdk/workflow
npm install workflow @ai-sdk/workflow
yarn add workflow @ai-sdk/workflow
bun add workflow @ai-sdk/workflow
You still need ai, zod, and @github-tools/sdk as documented in Installation.
Minimal durable workflow
createDurableGithubAgent returns a DurableGithubAgent with two methods:
.stream()— real-time output to aWritableStream(for chat UIs).generate()— non-streaming, returns the full text response (for bots, background jobs, webhooks)
Both methods execute each tool call as a durable workflow step with automatic retries.
Streaming (chat UI)
import { createDurableGithubAgent } from '@github-tools/sdk/workflow'
import { getWritable } from 'workflow'
import type { ModelCallStreamPart, ModelMessage } from 'ai'
export async function durableGithubChat(
messages: ModelMessage[],
token: string,
model: string
) {
'use workflow'
const writable = getWritable<ModelCallStreamPart>()
const agent = createDurableGithubAgent({
model,
token,
preset: 'code-review',
requireApproval: true,
})
await agent.stream({ messages, writable })
}
Non-streaming (bot / background job)
For non-streaming use cases (bots, webhooks, background jobs), use createGithubAgent inside a "use step" function. This gives you the full tool loop while keeping the step durable:
import { createGithubAgent } from '@github-tools/sdk'
async function runAgentTurn(prompt: string) {
'use step'
const agent = createGithubAgent({
model: 'anthropic/claude-sonnet-4.6',
preset: 'code-review',
requireApproval: false,
})
const { text } = await agent.generate({ prompt })
return text
}
export async function reviewWorkflow(prompt: string) {
'use workflow'
await runAgentTurn(prompt)
}
Wire it into your framework
Workflows are plain exported functions — your framework's Workflow integration starts the run from an API route and streams results to the client.
Place workflows under server/workflows/ and start them from a Nitro server route:
import { start } from 'workflow/api'
import { durableGithubChat } from '../workflows/durable-chat.workflow'
export default defineEventHandler(async (event) => {
const { messages, model } = await readBody(event)
const session = await requireUserSession(event)
const run = await start(durableGithubChat, [messages, session.secure.githubToken, model])
setHeader(event, 'x-workflow-run-id', run.runId)
return run.readable
})
Export workflows from any module and start them from an App Router route handler:
import { start } from 'workflow/api'
import { durableGithubChat } from '@/workflows/durable-chat.workflow'
export async function POST(req: Request) {
const { messages, model } = await req.json()
const run = await start(durableGithubChat, [messages, process.env.GITHUB_TOKEN!, model])
return new Response(run.readable, {
headers: { 'x-workflow-run-id': run.runId },
})
}
examples/pr-review-agent/ starter — a ~60-line PR review agent with multi-turn durable sessions and evlog AI observability. Full walkthrough: Chat SDK.Presets and options
All presets (code-review, issue-triage, repo-explorer, ci-ops, maintainer) work with createDurableGithubAgent. Options mirror createGithubAgent for model, token, preset, instructions, and temperature, with additional pass-through for WorkflowAgentOptions fields like experimental_telemetry, onStepEnd, onEnd, and prepareStep. Use stopWhen (for example stepCountIs(n)) instead of the deprecated maxSteps parameter.
Approval control and durable agents
requireApproval maps to needsApproval on write tools. When a tool needs approval, WorkflowAgent pauses the workflow, emits an approval request to the stream, and resumes when the user approves or denies — the same UX as createGithubAgent, but durable across restarts.
Wire the client with WorkflowChatTransport and a GET reconnect route ({api}/{runId}/stream) so long runs survive timeouts. The demo chat app toggles this with the shield icon.
For richer approval policies — 'once' per session, input-dependent predicates — use eve agents.
Standard agents vs durable agents
createGithubAgent | createDurableGithubAgent | |
|---|---|---|
| Import | @github-tools/sdk | @github-tools/sdk/workflow |
| Runtime | In-process ToolLoopAgent | DurableGithubAgent inside "use workflow" |
| Methods | .generate(), .stream() | .generate(), .stream() |
| Retries / resume | You handle | Workflow-managed durable steps |
requireApproval | Supported | Supported (via needsApproval on tools) |
Durable steps on every tool
Even if you do not use createDurableGithubAgent, spreading createGithubTools() inside a workflow still benefits from per-tool "use step" boundaries when the AI SDK executes tools — each GitHub operation remains a proper workflow step. The durable agent entry point additionally durably wraps the LLM loop itself.