Back to AI guides

Flue: Headless, Programmable AI Agent Framework from the Astro Team

Stanley Ulili
Updated on June 8, 2026

Flue is an open-source TypeScript framework for building AI agents, developed by the Astro team. It was originally built to automate AI workflows inside Astro's own GitHub repositories. Its design is headless and programmable: agents can run without a human present, triggered by API calls, webhooks, or cron jobs, and deployable to Node.js or Cloudflare Workers.

The harness concept

Flue's documentation defines an AI agent as an LLM running inside a harness. The LLM provides reasoning capability; the harness provides the tools, context, memory, and environment the LLM needs to interact with external systems and complete tasks.

Without a harness, an LLM responds to individual API calls with no persistent state and no tool access. Flue is the programmable harness layer: it provides session management, tool and skill execution, sandbox environments, and a structured output format.

Documentation page for "What is an agent?" illustrating the concept of an LLM running inside a harness

Installation and setup

 
mkdir flue-tutorial && cd flue-tutorial
 
npm install @flue/runtime
 
npm install --save-dev @flue/cli

Create a .env file with your LLM provider API key:

.env
ANTHROPIC_API_KEY="your-anthropic-api-key-here"

Initialize the project configuration:

 
npx flue init --target node

This creates flue.config.ts:

flue.config.ts
import { defineConfig } from '@flue/cli/config';

export default defineConfig({
  target: 'node',
});

target can be 'node' (Node.js server using Hono) or 'cloudflare' (Cloudflare Worker with Durable Objects for persistence).

Flue documentation showing the installation and initialization commands

Creating an agent

Flue looks for agent definitions in an agents/ directory. The filename becomes the agent's ID.

 
mkdir agents
agents/hello-world.ts
import { createAgent } from '@flue/runtime';

export default createAgent(() => ({
  model: 'anthropic/claude-3.5-sonnet',
  instructions: 'Tell a funny "hello world" engineering joke.',
}));

Connect to the agent interactively:

 
npx flue connect hello-world local-session

local-session is the instance ID. It identifies this conversation and enables session persistence across interactions.

After the agent responds, Flue prints a JSON summary:

Final JSON output in the terminal with response text, token usage, cost, and model ID

The output includes text (full response), usage (input and output token counts), cost (estimated API cost broken down by input, output, and cache), and model (the model ID used).

Building a workflow with a skill

Workflows perform a finite unit of work from input to result. They live in a workflows/ directory and export a run function.

A skill is a Markdown file containing instructions for the LLM on how to perform a specific reusable task. Flue imports it as structured context.

workflows/yt-titles.ts
import { createAgent, type FlueContext } from '@flue/runtime';
import { readFile } from 'node:fs/promises';
import titleScore from '../skills/title-score/SKILL.md' with { type: 'skill' };

const agent = createAgent(() => ({
  model: 'anthropic/claude-3.5-sonnet',
  instructions:
    'Study the provided script and generate 10 clickbait YouTube titles. Rank these titles from best to worst using the title-score skill. Just give me the titles and scores in a table and nothing else.',
  skills: [titleScore],
}));

export async function run(init: any, payload: FlueContext<{ path: string }>) {
  const harness = await init(agent);
  const session = await harness.session();
  const script = await readFile(payload.path, 'utf8');
  const response = await session.prompt(script);
  return { summary: response.text };
}

Code for the YouTube titles workflow showing the imported skill and the run function structure

Sandboxes

The default sandbox is just-bash: an in-memory TypeScript reimplementation of a Bash shell. It requires no Docker container or virtual machine, making it fast and cheap for agents that do not need filesystem access.

For workflows that need to read local files or run external scripts, the local sandbox provides direct filesystem access:

workflows/yt-titles.ts
import { local } from '@flue/runtime/node';

const agent = createAgent(() => ({
  model: 'anthropic/claude-3.5-sonnet',
  instructions: '...',
  skills: [titleScore],
  sandbox: local(),
  cwd: '/path/to/project/skills/title-score',
}));

sandbox: local() replaces the in-memory sandbox with the local machine's filesystem. This is less isolated but necessary when the agent needs to read files or execute local scripts.

Content of a SKILL.md file showing how instructions and bash commands are defined for the agent

Exposing a workflow as an HTTP endpoint

Add a route handler to the workflow file:

workflows/yt-titles.ts
import type { WorkflowRouteHandler } from '@flue/runtime/node';

export const route: WorkflowRouteHandler = async (_c, next) => {
  // add authentication or validation here
  return next();
};

Build the server:

 
npx flue build --target node

This produces dist/server.mjs.

 
PORT=8080 node dist/server.mjs

Trigger the workflow:

 
curl -X POST http://localhost:8080/workflows/yt-titles \
  -H "Content-Type: application/json" \
  -d '{"path": "/path/to/your/script.md"}'

The server responds immediately with a runId:

 
{"status":"accepted","runId":"workflow:yt-titles:01KT..."}

Poll for the result:

 
curl http://localhost:8080/runs/workflow:yt-titles:01KT...

JSON response from a GET request showing the completed workflow status and the summary result with ranked titles

Final thoughts

Flue's sandbox design is its most practically useful feature for cost management. The default just-bash in-memory sandbox means most agents incur no infrastructure cost beyond the API call itself. The local() sandbox is an explicit opt-in for cases that require it, keeping the default path cheap and fast.

The createAgent + SKILL.md + run function pattern is low-ceremony. Most of the agent's behavior is defined in Markdown rather than code, which makes it easy to adjust instructions without restructuring the application.

For teams using Astro who want headless AI agent capabilities without building their own orchestration layer, Flue is a natural fit. For teams on other stacks, the same principles apply; the main consideration is whether deploying to Node.js or Cloudflare Workers fits the existing infrastructure.

Documentation and source code are at github.com/floatplane/flue.

Got an article suggestion? Let us know
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.