# flue

withastro's "agent harness framework" — a TypeScript runtime that takes the Claude Code architecture (markdown-driven skills, AGENTS.md, sandbox, tools, sessions) and ships it as a programmable headless library. No TUI, no GUI, no human in the loop assumed. The pitch from the homepage:

> Not another SDK. Build powerful, autonomous agents with Flue's programmable TypeScript harness.

> Stop renting someone else's agent. Off-the-shelf AI tools are built for a general audience and struggle to fit to your product, your data, your customers, or your exact workflows.

The README's framing is sharper: *"think Astro or Next.js, but for agents — write once, build, and deploy anywhere."* Targets: Node.js, Cloudflare Workers (with Durable Objects for session state), GitHub Actions, GitLab CI/CD.

## Why this is interesting

Most agent frameworks (LangChain, AI SDK, Mastra, CrewAI) are *libraries* — you import them into a host process and they hand you completion + tool-loop primitives. Flue is a *framework* in the Astro/Next sense: it owns the build, the dev server, the deploy artifact, the session store, and the URL routing. The agent file at `.flue/agents/<name>.ts` is the unit of code; the harness is the runtime.

That changes what you're shaped to build. The README's four examples — quickstart translator, R2-backed support agent, CI issue triage, full Daytona-container coding agent — are all production-deploy-shaped from line one. Each one declares `triggers` (webhook / CLI / nothing), gets a typed result schema via valibot, and runs under a sandbox the agent didn't have to think about.

## The virtual-sandbox bet

The load-bearing technical choice: **default sandbox is virtual, not container.** Powered by [just-bash](https://github.com/vercel-labs/just-bash) (vercel-labs), an in-process bash-like interpreter with a pluggable in-memory filesystem. Per the README:

> A virtual sandbox is going to be dramatically faster, cheaper, and more scalable than running a full container for every agent, which makes it perfect for building high-traffic/high-scale agents.

This is the inverse of how every other "agent sandbox" tool in the [[sandboxing-ai-agents]] taxonomy goes. [[bubblewrap-dev-env]] gives you Linux namespaces; [[hazmat]] gives you a macOS Seatbelt + dedicated user; [[toolbox/superhq|superhq]] gives you a microVM per session. Flue says: for most agents, the agent doesn't need a real OS. It needs to grep some files, write some output, maybe shell out. So default to a Bash-shaped runtime that's a JS object, and only spin up a Daytona container or `sandbox: 'local'` when the agent genuinely needs `git clone` + `npm install`.

The Cloudflare integration sells the choice — `getVirtualSandbox(env.KNOWLEDGE_BASE)` mounts an R2 bucket as the agent's `/`, and the agent searches it with grep/glob/read tools without booting a container. At Workers pricing, that's the only way "support agent per customer per request" pencils out.

Three sandbox modes shipped:

- `getVirtualSandbox(...)` — default, in-process, R2/KV-backable on Cloudflare.
- `'local'` — mount host filesystem at `/workspace`. For CI where the repo is already checked out.
- `daytona(sandbox)` — full Linux container via [Daytona](https://www.daytona.io). Cached after first build.

## Markdown is the program

> They require very little code to run — most of the "logic" lives in Markdown: skills, context, and `AGENTS.md`.

Same `AGENTS.md` + `.agents/skills/` convention Claude Code, Cursor, and the cross-harness skill ecosystem (see [[mcp-vs-skills]], [[impeccable]], [[toolbox/agent-skill-linter]]) have settled on. Skills referenced by frontmatter `name:` *or* relative path:

```ts
const r = await session.skill('triage', { args: { issueNumber: 42 } });
const r = await session.skill('triage/reproduce.md', { ... });
```

Path references exist specifically for skill packs that group multiple stages under one directory.

## Programmable primitives

The session API is small enough to fit in head:

- `session.prompt(text, opts)` — send a user turn, get a typed result back (valibot schema).
- `session.skill(name, opts)` — run a markdown-defined skill with `args:`.
- `session.task(text, opts)` — spawn a one-shot detached child agent in a fresh session, sharing the parent's sandbox. The same `task` tool is exposed to the LLM during `prompt()`/`skill()`, so the model can delegate parallel exploration itself.
- `session.shell(cmd, opts)` — direct shell-out, useful for setup steps before the LLM gets the wheel.

Tools are scoped per call:

```ts
const npm = defineCommand('npm');
const gh  = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } });

await session.skill('triage', { commands: [gh, npm], ... });
```

The secret stays in the host environment; the agent gets a `gh` binary it can run, but never reads `GH_TOKEN`. Same instinct as [[toolbox/superhq|superhq]]'s auth gateway, applied to per-tool scope.

## Roles (lightweight subagent overlays)

Roles can be set at agent / session / call level — precedence is `call > session > agent`. Crucially:

> Role instructions are applied as call-scoped system prompt overlays, not injected into the persisted user message history.

So switching to `role: 'reviewer'` for one prompt doesn't pollute the session's transcript. This is a meaningful design choice that most "subagent" implementations get wrong by stuffing role instructions into a system message that then haunts every later turn.

## Triggers and routing

Each agent file declares its triggers:

```ts
export const triggers = { webhook: true };  // HTTP
export const triggers = {};                  // CLI-only (flue run)
```

For HTTP, the agent ID is the URL's last path segment:

```
POST /agents/<agent-name>/<id>
```

Reuse the same `<id>` to continue a conversation; new id = fresh session. On Cloudflare, sessions are backed by Durable Objects and survive across requests; on Node.js, in-memory by default unless you supply a custom store.

## MCP

`connectMcpServer()` is for connecting to *remote* MCP servers from trusted host code:

```ts
const github = await connectMcpServer('github', {
  url: 'https://mcp.github.com/mcp',
  headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}` },
});
const agent = await init({ tools: github.tools, ... });
```

Defaults to streamable HTTP; legacy SSE via `transport: 'sse'`. The current version explicitly does **not** auto-detect transports, spawn local stdio MCP servers, or handle OAuth callbacks. Same instinct as the rest of the API — production-shaped, not dev-laptop-shaped.

## Install / run

```bash
npm i @flue/sdk @flue/cli

flue dev --target node          # watch-mode dev server, port 3583 ("FLUE")
flue dev --target cloudflare    # via wrangler

flue run hello --target node --id test-1 --payload '{"text":"hi","language":"fr"}'

flue build --target node          # single bundled .mjs
flue build --target cloudflare    # Workers + Durable Objects (wrangler deploy)
```

## Comparison points

- **vs [[adk-go]]** — Google's Go ADK gives you the same primitives (sessions, tools, A2A) but stays library-shaped; Flue commits to the framework shape and the multi-target deploy story.
- **vs [[botctl]]** — botctl is a *process manager* for long-running Claude bots with `BOT.md` files; Flue is the framework you'd build the bot itself in. Composable, not competing.
- **vs [[charlie-daemons]]** — Charlie's daemons are markdown-defined autonomous AI processes orchestrated by their platform; Flue is the open framework for the same shape, deploy-it-yourself.
- **vs [[gomodel]]** — gomodel is a process-level OpenAI-compatible gateway; Flue talks to providers directly via model identifiers like `anthropic/claude-sonnet-4-6` or `openrouter/moonshotai/kimi-k2.6` (same syntax as OpenRouter).
- **vs [[crabtrap]]** — Brex's crabtrap is the LLM-as-judge HTTP proxy that secures agents in production; Flue is what's *behind* the proxy. Pair, don't pick.
- **vs Dosu / Greptile / CodeRabbit** — the homepage explicitly names these as the off-the-shelf vendors Flue replaces.

## Status / caveats

- 1,800 stars three months in (created 2026-02-07), 91 forks, 8 open issues, last push today (2026-05-03). Apache-2.0.
- README opens with **"Experimental — APIs may change."** v0.0.x lives on its own branch; current main is the rewrite.
- Cloudflare and Node.js targets ship today; "more coming in the future" for build outputs.
- No stdio MCP, no OAuth handling, no transport auto-detection — by design for v1, but worth knowing if your agent depends on local MCP servers.
- Backed by withastro (the Astro framework org), so the bus factor is "as healthy as Astro itself," which is the strongest signal here. Filed as regular toolbox, not [[toolbox/watchlist|watchlist]].

## Repo

[github.com/withastro/flue](https://github.com/withastro/flue) — homepage [flueframework.com](https://flueframework.com) · 1.8k★ · Apache-2.0 · TypeScript · `main` branch (current rewrite)

Source: [[flue-readme]].
