Flue: the open framework for building AI agents
By Flavio Copes
A guide to Flue, the open TypeScript framework for AI agents and workflows from Astro co-founder Fred Schott, covering agents, workflows and durability.
Fred Schott, the co-founder of Astro, shipped a new framework. It’s not for websites. It’s for AI agents.
It’s called Flue, and the current line is 2.x (@flue/[email protected] / @flue/[email protected] as of this update).
The pitch: Flue is to agents what Astro is to websites. A TypeScript framework with good developer experience, built on open pieces, and not locked to any one vendor.
In this post I’ll walk through what Flue is, the problem it solves, and how its main parts fit together.
What is Flue?
Flue is a TypeScript framework for building AI agents.
You pick any LLM, write your agent, and deploy it anywhere. That’s the whole pitch.
A few things set it apart:
- Open. You’re not tied to one model provider or one cloud.
- Durable. Agents survive restarts and outages, and pick up where they left off.
- Familiar. It’s built on tools you already use, like Vite.
Under the hood, Flue runs on Pi, an open “agent harness” used by tools like OpenClaw. (A harness is the thing that actually runs the model in a loop, calling tools and feeding results back.) It uses Vite to build, and a system called Durable Streams to move events around without losing them.
You don’t need to know any of that to use Flue. It’s just good to know the foundation is solid.
The problem Flue solves
A demo agent is easy. You wire up an LLM, give it a prompt, and it works.
A production agent is hard. Servers restart. Model providers time out. A tool call gets cut off halfway. A conversation that was going fine loses its history.
That last mile, getting an agent stable enough for real use, is the part teams keep struggling with. Flue exists to handle it for you.
Agents first (workflows are how you drive them)
In Flue 2, the unit you write is an agent. A “workflow” in the docs is not a second primitive with its own file type. It’s the pattern you use to drive an agent: flue run from a shell, a Node script with start() / init(), the Agent SDK over HTTP, or a durable orchestrator like Cloudflare Workflows / Inngest / Temporal.
- An agent is the durable conversation. You give it a model, tools, skills, and instructions.
- A workflow is how you call that agent from CI, a cron job, or a multi-step product flow.
Same runtime. Different ways to poke it. Let’s look at an agent first.
Your first agent
An agent module starts with the 'use agent' directive. Hooks like useModel() set the model. The function’s return value is the instructions (the system prompt):
// src/agents/assistant.ts
'use agent'
import { useModel } from '@flue/runtime'
export function Assistant() {
useModel('anthropic/claude-sonnet-4-6')
return 'You are a helpful assistant. Keep replies short.'
}
Run it locally with the CLI:
npx flue run src/agents/assistant.ts --message "Say hello in five words or fewer."
Pass --id to continue the same conversation across runs. Conversations persist in the project’s configured database.
Tools: bounded jobs inside an agent
When you need a bounded job (summarize a ticket, look up an order), don’t invent a second framework concept. Mount a tool on the agent with defineTool / useTool. The model decides when to call it; your code decides what happens:
import { defineTool } from '@flue/runtime'
import * as v from 'valibot'
export const lookupOrder = defineTool({
name: 'lookup_order',
description: 'Look up one order by id and return its current status.',
input: v.object({ orderId: v.string() }),
async run({ data }) {
const order = await orders.get(data.orderId)
return { output: { status: order.status, eta: order.eta } }
},
})
'use agent'
import { useModel, useTool } from '@flue/runtime'
import { lookupOrder } from '../tools/lookup-order.ts'
export function OrderAssistant() {
useModel('anthropic/claude-sonnet-4-6')
useTool(lookupOrder)
return 'Help customers check the status of their orders.'
}
My advice is to keep as much as you can in tools your code owns. The more your code decides, the fewer surprises. Let the agent improvise only where the task is open-ended.
Agents also burn far more tokens than a tight tool-driven flow, since every step carries history along. I built a free agent cost visualizer to estimate what a multi-step agent run costs before you build it.
What goes into an agent
An agent is only as good as the context you give it. Let’s go through the pieces.
Instructions
Instructions are the job description, in plain English. In Flue 2 that’s the string your agent function returns:
return `
Triage a bug report end-to-end: reproduce the bug,
diagnose the root cause, verify whether the behavior is
intentional, and attempt a fix.`
Tools
A tool is an action the agent can take: call an API, query a database, reply to an issue. You define it with defineTool, then mount it with useTool. The model decides when; you decide what’s available.
Skills
A skill is a reusable bit of know-how. If you’ve used skills with coding agents, this is the same idea. Skills can be imported from npm too.
Sandboxes
A sandbox is a safe, walled-off computer the agent can work in. It can run commands and edit files there without touching your real machine. Mount one with the sandbox hooks in the Flue docs (local, or a remote provider like Daytona or E2B).
Subagents
For big jobs, an agent can hand work to subagents through the built-in task tool. You set up specialized roles, and the main agent passes each task to the right one.
Driving agents as workflows
Once you have an agent, you can script around it. The smallest workflow is one flue run:
flue run src/agents/triage.ts --message "Triage issue 17307." --id issue-17307
From Node, start() boots the runtime in-process and init() gives you a conversation handle:
import { init } from '@flue/runtime'
import { sqlite, start } from '@flue/runtime/node'
import { Reporter } from '../src/agents/reporter.ts'
await using flue = await start({
agents: [Reporter],
db: sqlite('./nightly.db'),
})
const reporter = init(Reporter, { id: 'nightly-2026-09-11' })
const receipt = await reporter.dispatch('Produce the nightly report.')
const reply = await reporter.read(receipt)
console.log(reply.text)
For multi-step product flows that must survive crashes, put those dispatch / read calls inside a durable engine (Cloudflare Workflows, Inngest, Temporal). Flue already makes each admitted send durable; the outer workflow checkpoints the receipts between steps.
Channels: connecting agents to Slack and friends
An agent is no use if nobody can reach it. A channel is the doorway between your agent and a place like Slack, GitHub, Linear, Discord, or Teams.
The channel handles the incoming events and the verification boilerplate, so you don’t have to. You add one with the CLI:
flue add channel slack
Your coding agent gets a Markdown guide and wires the integration into the project. Same idea for sandboxes, databases, and tooling:
flue add sandbox daytona
flue add database postgres
flue add tooling opentelemetry
Durability: agents that don’t lose their memory
This is the part I like most.
A real agent has to survive failure. The server restarts, a provider times out, a tool call gets cut off. A durable agent rides through all of that and keeps going, without losing the conversation.
How? Flue borrows a trick from databases: the log is the source of truth.
Picture a flight recorder. Every prompt, every model response, every tool result gets written down to a record that only ever gets added to. That record is what Durable Streams gives you.
So when one process dies, another reads the record and continues from the last step. The user’s conversation isn’t lost.
In practice:
- Work that was accepted is never lost.
- Interrupted sessions resume on their own.
- Clients reconnect without starting over.
And you don’t write any of that recovery code. You get it for free. For side effects that must finish (payments, provisioning), mark the tool durable: true and wrap each effect in step.do(...) so recovery can skip completed steps.
A frontend with @flue/react
Once your agent runs behind a server, you’ll usually want a UI. That’s what @flue/react is for.
It gives you hooks that stream live data into your React app, so you don’t have to wire up the realtime part yourself:
import { createFlueClient } from '@flue/sdk'
import { FlueProvider, useFlueAgent } from '@flue/react'
const client = createFlueClient({ baseUrl: '/api' })
function Chat() {
const { messages, status, sendMessage } = useFlueAgent({
name: 'triage',
id: 'ticket-8472',
})
// ...
}
useFlueAgent() connects to a running agent and streams its messages. If you’re not using React, @flue/sdk lets you talk to a deployed agent from anywhere: another service, a script, whatever you have.
A CLI built for coding agents
Adding an integration to a framework used to mean a package, a setup guide, and an installer that did a few fixed steps. The real work, fitting it into your actual project, was still on you.
Flue takes a different route. flue add hands your coding agent a Markdown guide with everything it needs to finish the job inside your real codebase.
Your coding agent reads the guide and wires the integration into your project. To upgrade one later, run flue update.
The team calls it “shadcn, for your agents.” That’s a fair way to picture it.
More in Flue 2.x
There’s more in the current release:
- Observability — send telemetry to OpenTelemetry, Braintrust, Sentry, or your own.
- Databases — adapters for Postgres, MySQL, Redis, MongoDB, and Supabase.
- Image inputs — agents can take images, not just text.
- npm skills — share and reuse skills as packages.
- Offline docs —
npx flue docs search/flue docs readso your coding agent can search Flue’s docs locally for the installed version.
On deploying: Flue targets Cloudflare and Node.js. Use vite dev / vite build for the server path. flue run alone is also a supported way to ship (CI, cron, personal tools) with no HTTP server.
Getting started
The quickest way to start is to let your coding agent do it. Flue gives you a prompt to paste in:
Read https://flueframework.com/start.md then help create my first agent…
Your agent reads the guide and scaffolds the project.
Want to set it up yourself? Install the packages and run init (examples use @flue/[email protected] / @flue/[email protected]):
npm install @flue/runtime @flue/cli
npx flue init . --target node
Or skip the interactive scaffold and hand-write flue.config.ts plus src/agents/assistant.ts from the getting started guide.
From there, agents go in src/agents/. Add vite + src/app.ts when you want an HTTP server.
My take
What I like about Flue is the philosophy. It’s open, it works with any LLM, it deploys anywhere, and it’s clearly built by someone who cares about how it feels to use.
If you’re building agents and you’ve hit the wall of getting them to production, Flue is worth a serious look.
The full announcement and docs are at flueframework.com.
Want me to talk about your product? You can sponsor this site.
Related posts about ai: