Map

flue

Toolbox toolboxtypescriptai-agentsagent-frameworkclaude-codesandboxcloudflaremcpTypeScriptApache-2.0 โ†ณ show in map Markdown
title
flue
type
toolbox
summary
Headless TypeScript agent harness with markdown skills and an in-process virtual bash sandbox
tags
typescript, ai-agents, agent-framework, claude-code, sandbox, cloudflare, mcp
language
TypeScript
license
Apache-2.0
created
2026-05-03
updated
2026-05-03

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 (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; 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. 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, agent-skill-linter) have settled on. Skills referenced by frontmatter name: or relative path:

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:

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 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:

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:

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

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 watchlist.

Repo

github.com/withastro/flue โ€” homepage flueframework.com ยท 1.8kโ˜… ยท Apache-2.0 ยท TypeScript ยท main branch (current rewrite)

Source: flue-readme.