---
title: "Vercel Workflow"
description: "Run a GitHub agent that resumes after a crash and waits as long as needed for an approval."
canonical_url: "https://github-tools.com/integrations/workflow"
---
# Vercel Workflow

> Run a GitHub agent that resumes after a crash and waits as long as needed for an approval.

## Overview

`createDurableGithubAgent()` builds a durable GitHub agent: an AI SDK [`WorkflowAgent`](https://ai-sdk.dev/docs/agents/workflow-agent) that runs inside a Vercel Workflow. Called from a `"use workflow"` function, every model call and every tool call runs as its own [step](https://github-tools.com/reference/glossary#step). The step's result is recorded and a failed step retries. After a crash or a redeploy, the run resumes from the last completed step instead of starting over.

<workflow-resume>



</workflow-resume>

Pick it when a run must survive restarts, or wait hours for someone to approve a write. Each GitHub tool's implementation already carries `"use step"`, so tools from `createGithubTools()` are steps inside any workflow. The durable agent adds the model calls to the same record.

## Install

<code-group>

```bash [pnpm]
pnpm add workflow @ai-sdk/workflow
```

```bash [npm]
npm install workflow @ai-sdk/workflow
```

```bash [yarn]
yarn add workflow @ai-sdk/workflow
```

```bash [bun]
bun add workflow @ai-sdk/workflow
```

</code-group>

Install the SDK, `ai` and `zod` as in [Installation](https://github-tools.com/start/installation), and set up Workflow for your framework with the [Workflow docs](https://vercel.com/docs/workflow).

The workflow writes the agent's output to the run's stream:

```ts [workflows/github-chat.ts]
import type { ModelCallStreamPart } from '@ai-sdk/workflow'
import { createDurableGithubAgent } from '@github-tools/sdk/workflow'
import type { ModelMessage } from 'ai'
import { getWritable } from 'workflow'

export async function githubChat(messages: ModelMessage[]) {
  'use workflow'

  const agent = createDurableGithubAgent({
    model: 'anthropic/claude-opus-5.5',
    preset: 'code-review',
    experimental_toolApprovalSecret: { environmentVariable: 'TOOL_APPROVAL_SECRET' },
  })

  await agent.stream({ messages, writable: getWritable<ModelCallStreamPart>() })
}
```

The route starts the run and converts its stream for the AI SDK UI:

```ts [app/api/chat/route.ts]
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'
import { convertToModelMessages, createUIMessageStreamResponse, type UIMessage } from 'ai'
import { start } from 'workflow/api'
import { githubChat } from '@/workflows/github-chat'

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json()
  const run = await start(githubChat, [await convertToModelMessages(messages)])

  return createUIMessageStreamResponse({
    stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
    headers: { 'x-workflow-run-id': run.runId },
  })
}
```

## Reconnect after a reload

On the client, `useChat` with `WorkflowChatTransport` from `@ai-sdk/workflow` reconnects to a run after a timeout or a reload. It needs a GET route at `{api}/{runId}/stream`, shown in the [`WorkflowAgent` docs](https://ai-sdk.dev/docs/agents/workflow-agent).

## Non-streaming

For bots, webhooks and background jobs, call `.generate()` from the workflow function. It runs the same loop, one step per model call and per tool call. It needs `@ai-sdk/workflow` 2.0.59 or later, and older versions throw `github_tools.DURABLE_GENERATE_UNAVAILABLE`.

```ts [workflows/review.ts]
import { createDurableGithubAgent } from '@github-tools/sdk/workflow'
import type { ModelMessage } from 'ai'

export async function review(messages: ModelMessage[]) {
  'use workflow'

  const agent = createDurableGithubAgent({
    model: 'anthropic/claude-opus-5.5',
    preset: 'code-review',
    requireApproval: false,
  })

  const result = await agent.generate({ messages })
  return { text: result.text, responseMessages: result.responseMessages }
}
```

The result of `.generate()` isn't serializable, so return the fields you need.

## Approval

Write tools ask by default, through `needsApproval` on each tool. When one asks, the agent writes the approval request to the stream and the workflow suspends. The request survives restarts, so a person can answer hours later. With `useChat`, the client answers with `addToolApprovalResponse`, as on the [AI SDK page](https://github-tools.com/integrations/ai-sdk#approval-in-a-chat-ui).

With `.generate()`, a write that needs approval ends the call with a `tool-approval-request` in `responseMessages`. Store the messages and ask the person. Then run the workflow again with the history plus a `tool-approval-response`.

`requireApproval` takes `true`, `false` or a per-tool object, but not `'auto'`. A write that [`guardrails`](https://github-tools.com/approval#guardrails) block skips the approval request and fails with the reason.

### Sign the answer

The answer comes back inside history the client sends, so sign it. The workflow takes `experimental_toolApprovalSecret: { environmentVariable: 'TOOL_APPROVAL_SECRET' }`, the name of the variable rather than its value. Each signing step reads the secret from its own environment, so the value never enters the run's recorded state. Set the variable on every worker that can issue or resume an approval.

## Set agent options

`createDurableGithubAgent` takes the GitHub options of [`createGithubAgent`](https://github-tools.com/integrations/ai-sdk#creategithubagent): `token`, `preset`, `requireApproval`, `overrides`, `context`, `instructions`, `additionalInstructions`, `guardrails`, `dryRun` and the commit identity. It also takes every `WorkflowAgent` option, such as `stopWhen`, `providerOptions`, `prepareStep` and `telemetry`. [API](https://github-tools.com/reference/api) has the full signature.

## FAQ

<accordion>
<accordion-item label="Which versions do I need?">

`workflow` 4.5 or 5, and `@ai-sdk/workflow` 1.0.16 or 2. Both are optional peers of the SDK. `.generate()` needs `@ai-sdk/workflow` 2.0.59 or later.

</accordion-item>

<accordion-item label="Why can't I use 'auto'?">

The decision call can't run inside a workflow function, so neither `requireApproval: 'auto'` nor `preset: 'auto'` is supported.

</accordion-item>

<accordion-item label="How many steps does .generate() take?">

`.generate()` stops after 20 steps unless you pass `stopWhen`.

</accordion-item>

<accordion-item label="Can a whole agent turn be one step?">

Yes, call `createGithubAgent` inside a `"use step"` function. A failure then retries the full turn rather than the one call that failed. [Chat SDK](https://github-tools.com/integrations/chat-sdk) uses this shape.

</accordion-item>
</accordion>


## Sitemap

See the full [sitemap](https://github-tools.com/sitemap.md) for all pages.
