---
description: Upgrade an application from Flue 1.0.0-beta.x to the current release — build, routing, agents, tools, workflows, SDK, and deployment.
title: Migration Guide | Flue
image: https://flueframework.com/docs/og4.jpg
---

# Migration Guide

Last updated Jul 17, 2026[View as Markdown](https://nightly.flueframework.com/docs/guide/migration/index.md)

This guide migrates an application from **Flue `1.0.0-beta.x`** to the new v2.0.0 preview release. It is written for working beta applications: every section pairs the beta API with its replacement, and the [checklist](#migration-checklist) at the end orders the work.

The release is a redesign, not an increment. Five conceptual changes drive almost every mechanical one:

* `flue build` / `flue dev` CLI commands — replaced by [Vite](https://nightly.flueframework.com/docs/guide/deploy/) with the `flue()` plugin; `vite dev` and `vite build` are the only commands.
* The auto-mounted `flue()` router and discovery by directory — replaced by [explicit routing](https://nightly.flueframework.com/docs/guide/routing/) in `app.ts`; the ['use agent' scan](https://nightly.flueframework.com/docs/guide/building-agents/#use-agent-directive) registers agents.
* `defineAgent(async initializer => config)` with a config bag — the agent **is** the function now: an exported capitalized agent function composing behavior with [Flue Hooks](https://nightly.flueframework.com/docs/reference/agent-hooks-api/); `defineAgent` is gone entirely.
* Workflows (`defineWorkflow`, `invoke()`, runs, run events) — removed. Use awaited [init() handles](https://nightly.flueframework.com/docs/guide/building-agents/#standalone-scripts), [durable tools](https://nightly.flueframework.com/docs/guide/tools/#durable-tools), or your own orchestrator.
* The deployment-wide SDK client (`client.agents.*`, `client.workflows.*`) — replaced by a [conversation-scoped client](https://nightly.flueframework.com/docs/sdk/create-flue-client/): one client per conversation URL.

## Before you start: persisted state resets

The current release stores Flue schema **version 7**; the beta stored version 5\. Pre-1.0 persisted schemas are **reset-only** — the runtime rejects an older database before any application code runs, and there is no in-place migration.

* If beta conversation state is disposable, plan a drained deployment: retire the old agents (on Cloudflare, with `deleted_classes` migrations) and create fresh ones. Application data that shares an agent’s storage (a `base`/`wrap` DO extension, values written beside Flue’s tables) is deleted with it — export anything you need first.
* If beta state must survive, export it through the beta application _before_ upgrading, and re-seed after.

Everything else in this guide can be staged; this one is a hard boundary.

## Build and dev commands

`flue build` and `flue dev` are removed. A Flue application is now a Vite project: add `@flue/vite` (and on Cloudflare, `@cloudflare/vite-plugin`) and author `vite.config.ts` — `flue()` must come **before** `cloudflare()`:

```ts
import { cloudflare } from '@cloudflare/vite-plugin'; // Cloudflare target only
import { flue } from '@flue/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [flue(), cloudflare()],
});
```

* `flue dev --target cloudflare` — now `vite dev`.
* `flue build --target cloudflare` — now `vite build`.
* `flue build` (Node) — now `vite build`, then `node dist/server.mjs`.
* Target selection via `--target` — now auto-detected from the plugin array, or set `target` in `flue.config.ts`.

On Cloudflare the plugin generates two inputs the Cloudflare plugin consumes — `.flue-vite/_entry.ts` (the Worker entry) and `.flue-vite.wrangler.jsonc` (your authored `wrangler.jsonc` merged with generated bindings). Add both to `.gitignore`. Build output, preview, and deploy belong to `@cloudflare/vite-plugin`; deploy against the config it emits into `dist/`.

`flue run` remains for one-shot local execution, with new flags: `flue run <path> --message "..." [--name <agent>] [--id <id>] [--data '<json>']`. The beta’s workflow-oriented `--input` is gone with workflows. The HTTP-based `flue run <name>` form is gone too — the command takes an agent module path and executes in-process, so `--server`, `--header`, `--target`, `--root`, `--output`, and `--config` have no replacement; to call a deployed server, use the SDK’s conversation client instead. In `flue.config.*`, the retired `root` and `output` fields are rejected by strict validation everywhere except `flue run`, which silently drops unknown keys — delete them.

## Routing: the auto-router is gone

The beta’s `flue()` router (`app.route('/', flue())` from `@flue/runtime/routing`, or the generated default app) no longer exists. `app.ts` is now **required**, and it mounts every route explicitly:

```ts
import { flue } from '@flue/runtime/routing';
const app = new Hono();
app.use('/agents/*', requireUser);
app.route('/', flue()); // agents, workflows, channels — discovered and mounted
export default app;
```

```ts
import { createAgentRouter } from '@flue/runtime/routing';
import { Hono } from 'hono';
import { Triage } from './agents/triage.ts';

const app = new Hono();
app.use('/agents/*', requireUser);
app.route('/agents/triage', createAgentRouter(Triage)); // explicit, per agent
export default app;
```

* `createAgentRouter(fn)` is a pure router factory serving `POST /:id`, `GET|HEAD /:id`, `POST /:id/abort`, and `GET /:id/attachments/:attachmentId` relative to the mount. URL shapes are yours.
* **Registration comes from the ['use agent' scan](https://nightly.flueframework.com/docs/guide/building-agents/#use-agent-directive), not the mount.** A dispatch-only agent stays unmounted and still works; mounting registers nothing.
* Workflow routes (`POST /workflows/<name>`, `/runs/<runId>`) are gone with workflows, as are the `runs` module export and `WorkflowRouteHandler`/`WorkflowRunsHandler` types.

## Defining an agent

The beta’s async initializer returning a config bag becomes a **synchronous agent function** composing behavior with hooks, in a module marked by the `'use agent'` directive:

```ts
import { defineAgent } from '@flue/runtime';

export default defineAgent(async ({ id, env }) => ({
  model: 'anthropic/claude-sonnet-4-6',
  instructions: `Help with ticket ${id}.`,
  tools: [lookupOrder],
  skills: [refundsSkill],
  subagents: [reviewerProfile],
  sandbox: bash(myFactory),
  cwd: '/workspace',
  durability: { maxAttempts: 5 },
}));
```

```ts
'use agent';
import {
  type AgentProps,
  useModel,
  useSandbox,
  useSkill,
  useSubagent,
  useTool,
} from '@flue/runtime';

export function Support({ id }: AgentProps) {
  useModel('anthropic/claude-sonnet-4-6');
  useSandbox(myFactory, { cwd: '/workspace' });
  useTool(lookupOrder);
  useSkill(refundsSkill);
  useSubagent({ name: 'reviewer', description: '…', agent: Reviewer });
  return `Help with ticket ${id}.`;
}
Support.durability = { maxAttempts: 5 };
```

The agent **is** the exported function — there is no wrapper and no config bag. Durability became the `durability` static: assigned on the function like `agentName`/`initialData`, so it applies wherever the agent runs (mounted, dispatch-only, `start()`, `flue run`) and stays readable when a render crashes.

Field-by-field:

* `model` — now [useModel(model, options?)](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usemodel): **required**, exactly once per render, root render only.
* `instructions` — now the agent function’s return string; [useInstruction()](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#useinstruction) appends more.
* `tools` — now [useTool()](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usetool) per tool.
* `skills` — now [useSkill()](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#useskill) per skill.
* `subagents` (profiles) — now [useSubagent({ name, description, agent, model?, thinkingLevel? })](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usesubagent); the delegate is an agent function, not a profile.
* `thinkingLevel`, `compaction` — now `useModel(model, { thinkingLevel, compaction })`.
* `sandbox`, `cwd` — now [useSandbox(factory, { cwd })](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usesandbox): at most once per render; presence may be conditional.
* `durability` — now the `durability` static: `Support.durability = { maxAttempts: 5 }`. A static, not a hook, because the platform applies it when the function is not running; an environment-dependent policy goes in the assigned expression (`flag ? x : y`).
* `profile` — removed; compose with custom hooks (plain functions calling hooks) instead.
* `actions` — removed with Actions. Express reusable operations as tools.
* `description` (config) — deleted, no replacement.
* initializer `ctx.id` — now `AgentProps`: the root agent function receives `{ id }`.
* initializer `ctx.env` — now platform imports (`import { env } from 'cloudflare:workers'`) or `process.env`; the initializer context is gone.
* `async` initializer — the agent function **must be synchronous**; async work moves into tools, lifecycle hooks (`useAgentStart`/`useAgentFinish`), or resource factories such as the sandbox factory’s `createSessionEnv()`.

Rules that have no beta equivalent, because renders repeat:

* The agent function re-renders before every model turn. **Resources** (`useTool`, `useSkill`, `useSubagent`) may be conditional — changes are announced to the model as `resources` signals — and so may `useSandbox` (a presence flip swaps the environment at the next turn boundary, announced as an `environment` signal), `usePersistentState` (its storage is keyed by name), and the **event hooks** (`useAgentStart`, `useAgentFinish`, `useResponseStart`, `useResponseFinish` — each seam runs whatever the current render declares, at-least-once). The one invariant: `useDataWriter` names must be declared identically on every render.
* `defineAgentProfile` is removed. A subagent is `{ name, description, agent }` where `agent` is a plain agent function rendered per delegation; `model`/`thinkingLevel` inherit from the parent unless overridden. Inside a delegate render, `usePersistentState`, `useSandbox`, `useModel`, `useDataWriter`, `useDispatchMessage`, and the lifecycle/response hooks throw.
* Identity is the exported function’s name, or an `fn.agentName = '...'` **string-literal** static override; PascalCase and lower-kebab-case are both valid, and identities are unique per application. Renaming an agent function without an `agentName` pin is a storage-identity change; renaming the file changes nothing.

New capabilities you will likely reach for while migrating — durable per-instance state ([usePersistentState](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usepersistentstate)), creation data ([useInitialData](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#useinitialdata) \+ the `initialData` schema static), the delivered-message cursor ([useDelivery](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usedelivery)), self-dispatch ([useDispatchMessage](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usedispatchmessage)), client-facing data parts ([useDataWriter](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#usedatawriter)), lifecycle seams ([useAgentStart](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#useagentstart)/[useAgentFinish](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#useagentfinish)), and response metadata ([useResponseStart/useResponseFinish](https://nightly.flueframework.com/docs/reference/agent-hooks-api/#useresponsestart)).

## Tools

The tool contract keeps `defineTool({ name, description, input, output, run })`, with one rename and two new flags:

* **`run({ input })` → `run({ data })`.** The parsed-arguments field on `ToolContext` is now `data`; `signal` is unchanged and `log` (a `FlueLogger`) is always present. The pre-beta `parameters`/`execute` markers still throw.
* **`harness: true`** replaces session plumbing: the tool receives `harness` (`harness.prompt()` for model calls in the tool’s own scratch conversation, `harness.sandbox` for the environment). `harness.session()` and `FlueSession`/`FlueSessions` are gone — `prompt()` lives directly on the harness, and `session.task()` delegation is now the model-driven `task` tool over `useSubagent` declarations. The `task` tool’s `agent` parameter is required, and an unnamed task no longer clones the parent’s configuration — declare `useSubagent(GeneralSubagent)` for a blank fresh-context delegate (see [Subagents](https://nightly.flueframework.com/docs/guide/subagents/#the-general-purpose-delegate)).
* **`durable: true`** opts a tool into checkpointed execution: `run` receives `step`, side effects go through `step.do(name, fn)`, and recovery replays recorded step values instead of re-running them. This is the in-agent replacement for small workflow orchestration.

`harness.fs` is also gone: the harness exposes [harness.sandbox](https://nightly.flueframework.com/docs/reference/agent-api/#harnesssandbox), a `SessionEnv` carrying `exec`, the file verbs, `cwd`, and `resolvePath`. Adapters may not support every verb and may expose native accessors (for example Cloudflare Shell’s `shellWorkspace(harness.sandbox)`).

## Skills and markdown imports

Import-attribute syntax (`with { type: 'skill' }` and friends) is **removed**; the specifier decides:

* An import that resolves to a **`SKILL.md`** packages the whole skill directory and returns a `SkillReference` for `useSkill()`.
* Any **other `.md`** import is plain markdown text (a string), inlined at build time. To make one a skill, pass it through `defineSkill({ name, description, instructions })` — `defineSkill` writes frontmatter itself, so the file stays plain markdown. (A `?skill` import query was never released; do not use one.)
* Vite-native queries (`?raw`, `?url`) keep their usual meanings.

## Workflows are removed

`defineWorkflow`, `invoke()`, `listRuns()`, `getRun()`, workflow HTTP routes, `client.workflows.*`, `useFlueWorkflow()`, the `src/workflows/` discovery directory, the Workflow API, and workflow run events are all gone. There is no framework job abstraction to migrate _to_ — pick the smallest replacement that preserves your semantics:

1. **A single model operation with a returned value** (the common beta workflow): an awaited handle. `init(agent, { id })` addresses an instance; `await handle.dispatch(message)` delivers through the normal queue, waits for settlement, and resolves with the reply (`text`, `data`, `metadata`, `submissionId`). A failed or aborted run rejects with `AgentRunError`.  
```ts  
import { init } from '@flue/runtime';  
import { Summarizer } from './agents/summarizer.ts';  
const reply = await init(Summarizer, { id: `summary-${caseId}` }).dispatch(text);  
return reply.data.summary;  
```
2. **Checkpointed side-effect sequences inside an agent**: a `durable: true` tool with `step.do(...)`.
3. **Multi-step orchestration with its own durability, retries, and inspection** (what workflow runs gave you): an application-owned orchestrator. On Cloudflare, use a [Cloudflare Workflow](https://developers.cloudflare.com/workflows/) whose steps call `init(...).dispatch(...)` — one agent send per step, with the recorded step result standing in for the reply on re-execution. Run inspection (`getRun()`) has no framework replacement: reconcile from your orchestrator’s own state and from `submission_settled` observability events.

In standalone Node scripts (cron jobs, CI, tests), boot the runtime first with `start()` from `@flue/runtime/node`, passing agent functions (or `{ agent, name? }` entries); then `init()`/`dispatch()` work as they do in a server. See [Standalone scripts](https://nightly.flueframework.com/docs/guide/building-agents/#standalone-scripts).

## Dispatch and conditional sends

`dispatch(agent, request)` keeps its shape with two changes:

* The creation seed field is **`initialData`** (validated by the agent’s `initialData` schema static at creation, read with `useInitialData()`; ignored on sends that continue an existing instance). If your beta app abused the first message body to carry setup facts, move them here.
* **`uid` send conditions**: omit to continue-or-create; pass a previous receipt’s `uid` to continue only that incarnation (`AgentInstanceNotFoundError`/404 otherwise); pass `null` to create only (`AgentInstanceExistsError`/409 otherwise, carrying the existing uid). Conditions are checked at admission and create nothing on failure. `getAgentInstance(agent, id)` looks up `{ id, uid }` without sending.

A bare string is user-message shorthand everywhere a message is accepted. `dispatch()` remains fire-and-forget at durable admission; the awaited form is the `init()` handle above.

## Observability

Run-scoped events are gone with workflows; agent activity is observed directly. Register `observe(...)` as before, and migrate event handling:

* `run_start` / `run_end` — now `agent_start` / `agent_end`.
* `runId` correlation — now `instanceId` (the agent instance), `submissionId` (one delivered submission), and `dispatchId` (one dispatch delivery).
* Polling `getRun()` for the outcome — now `submission_settled` events (terminal outcome of recovered submissions) plus your own orchestrator’s state.
* Failed run inspection — now `operation` events with `isError`, carrying the failing operation kind.
* `createOpenTelemetryObserver()` from `@flue/opentelemetry` — now [createOpenTelemetryInstrumentation()](https://nightly.flueframework.com/docs/ecosystem/tooling/opentelemetry/), registered with `instrument(...)` instead of `observe(...)`; the `exportContent` option became the instrumentation-wide `content` policy, and the old custom model/tool content attributes are no longer emitted alongside the standard fields.

See the [Events Reference](https://nightly.flueframework.com/docs/reference/events/) for the full envelope (`v: 3`) and payload contract.

## SDK

The beta’s deployment-wide client is now **conversation-scoped**: construct one client per conversation URL — the agent’s mount URL plus the conversation id. There is no `baseUrl`, no agent-name addressing, and no `client.agents`/`client.workflows` namespaces.

```ts
// Beta
const client = createFlueClient({ baseUrl: '/api' });
await client.agents.send('support-assistant', ticketId, { message });
await client.agents.abort('support-assistant', ticketId);

// Now
const conversation = createFlueClient({ url: `/api/agents/support-assistant/${ticketId}` });
await conversation.send({ message, initialData });
await conversation.abort();
```

The conversation client exposes `send` (202 admission; returns `uid` and `submissionId`), `wait(admission)`, `observe()`, `history()`, `abort()`, and `attachmentUrl()`. `abort()` aborts the **conversation’s** in-flight and queued work — there is no per-submission abort — so shared conversations (an operator chat that also receives dispatched internal work) should account for that scope.

## React

`@flue/react` now exports only `useFlueAgent`. `FlueProvider` and `useFlueWorkflow` are removed.

```tsx
// Beta
<FlueProvider client={deploymentClient}>…</FlueProvider>;
const agent = useFlueAgent({ name: 'support-assistant', id });

// Now — pass the conversation URL, or a memoized conversation client
const agent = useFlueAgent({ url: `/api/agents/support-assistant/${id}` });
const agent = useFlueAgent({ client }); // useMemo the client — a new instance replaces the session
```

Messages remain Flue-owned parts-based values; new part kinds (`data-*` from `useDataWriter`, message `metadata` from the response hooks) should be narrowed, not assumed. `refresh()` and the dormant-when-`url`\-omitted behavior carry over.

## Cloudflare deployments

* **`FlueRegistry` is gone.** The beta’s deployment-wide registry DO indexed workflow runs; nothing replaces it. Append a `deleted_classes` migration for it, and for every `Flue<Name>Workflow` class.
* Generated classes are per-agent only: `export function Triage()` → class `FlueTriageAgent`, binding `FLUE_TRIAGE_AGENT` (one class per agent function; a file can carry several). Migration history stays user-authored — adding an agent is always the triple: the exported agent function in a `'use agent'` module, the mount (unless dispatch-only), and a `new_sqlite_classes` entry. Renames use `renamed_classes` — but remember the [schema reset](#before-you-start-persisted-state-resets): a beta-era database is rejected even under a renamed class, so beta agents are usually retired (`deleted_classes`) in favor of fresh identities.
* Your authored `wrangler.jsonc` is never modified; the build merges it into `.flue-vite.wrangler.jsonc` and the deployable config lands in `dist/`. Deploy with `wrangler deploy --config dist/<worker>/wrangler.json`.
* The generated entry exports every agent class plus your `app.ts` fetch handler; application-owned exports (your own DOs, Workflows) come from `cloudflare.ts`.

## CLI

`flue init`, `flue add`, `flue update`, and `flue docs` remain. `flue dev` and `flue build` are removed (Vite owns both). `flue run <path>` executes one agent module directly — `--message`, `--name` (select one agent by name when the module defines several), `--id`, `--data` (creation data), `--uid`/`--new`, `--json`. Durability comes from the agent’s own `durability` static, as everywhere else.

## Migration checklist

1. **Pins.** Replace `@flue/*@1.0.0-beta.x` with the current versions; add `@flue/vite`, `vite`, and (Cloudflare) `@cloudflare/vite-plugin`. Drop beta-era patches and vendored builds — re-verify each patched behavior against the new runtime before porting anything.
2. **Build.** Author `vite.config.ts` (`flue()` before `cloudflare()`); move package scripts to `vite dev`/`vite build`; gitignore the generated files.
3. **Routing.** Author explicit mounts in `app.ts`; delete `flue()` router usage; decide which agents are dispatch-only.
4. **Agents.** Convert each initializer to an exported capitalized agent function in a `'use agent'` module: hooks for behavior, statics (`agentName`, `initialData`, `durability`) for the contract, `AgentProps` for the id, platform env instead of `ctx.env`. Convert profiles to `useSubagent` agent functions.
5. **Tools.** Rename `run({ input })` to `run({ data })`; adopt `harness: true` where tools prompted sessions; consider `durable: true` for side-effect sequences.
6. **Skills.** Delete import attributes; let `SKILL.md` imports package themselves; wrap other markdown with `defineSkill` where needed.
7. **Workflows.** Replace each with the smallest fit: awaited `init()` handle, durable tool, or an application-owned orchestrator.
8. **Observability.** Migrate `run_*` handling to `agent_start`/`agent_end`/`submission_settled` and the `instanceId`/`submissionId`/`dispatchId` correlation fields.
9. **Clients.** Move SDK and React usage to conversation-scoped clients and `useFlueAgent({ url | client })`.
10. **Deployment.** Append `deleted_classes` for `FlueRegistry` and workflow classes; add `new_sqlite_classes` for new agents; plan the drained deployment for the schema reset.
11. **Verify.** Typecheck, tests, a production `vite build`, and a check of the built artifact (exports, merged wrangler config) before deploying.

## Docs Navigation

Current page: [Migration Guide](https://nightly.flueframework.com/docs/guide/migration/)

### Sections

* [Guide](https://nightly.flueframework.com/docs/guide/getting-started/)
* [Reference](https://nightly.flueframework.com/docs/reference/agent-api/)
* [CLI](https://nightly.flueframework.com/docs/cli/overview/)
* [SDK](https://nightly.flueframework.com/docs/sdk/overview/)
* [Ecosystem](https://nightly.flueframework.com/docs/ecosystem/)

### Introduction

* [Getting Started](https://nightly.flueframework.com/docs/guide/getting-started/)
* [Why Flue?](https://nightly.flueframework.com/docs/guide/why-flue/)
* [Migration Guide](https://nightly.flueframework.com/docs/guide/migration/)
* [Changelog](https://github.com/withastro/flue/blob/main/CHANGELOG.md)

### Guides

* [Project Layout](https://nightly.flueframework.com/docs/guide/project-layout/)
* [Agents](https://nightly.flueframework.com/docs/guide/building-agents/)
* [Agent Hooks](https://nightly.flueframework.com/docs/guide/agent-hooks/)
* [Models](https://nightly.flueframework.com/docs/guide/models/)
* [Tools](https://nightly.flueframework.com/docs/guide/tools/)
* [Skills](https://nightly.flueframework.com/docs/guide/skills/)
* [Subagents](https://nightly.flueframework.com/docs/guide/subagents/)
* [Sandboxes](https://nightly.flueframework.com/docs/guide/sandboxes/)
* [Routing](https://nightly.flueframework.com/docs/guide/routing/)
* [Database](https://nightly.flueframework.com/docs/guide/database/)

### Advanced

* [Deploy](https://nightly.flueframework.com/docs/guide/deploy/)
* [Schedules](https://nightly.flueframework.com/docs/guide/schedules/)
* [Channels](https://nightly.flueframework.com/docs/guide/channels/)
* [Evals](https://nightly.flueframework.com/docs/guide/evals/)
* [Observability](https://nightly.flueframework.com/docs/guide/observability/)
* [Durability](https://nightly.flueframework.com/docs/guide/durability/)

### Frontend

* [React](https://nightly.flueframework.com/docs/guide/react/)

### Targets

* [Cloudflare](https://nightly.flueframework.com/docs/guide/cloudflare-target/)
* [Node.js](https://nightly.flueframework.com/docs/guide/node-target/)