# Flue, Fred Schott, and the agent-harness moment in TypeScript

URL: https://www.thedeepfeed.ai/posts/2026-05-02-flue-agent-harness-framework/
Category: Tools
Published: 2026-05-02
Updated: 2026-06-23
Author: the-deep-feed
Tags: flue, agents, harness, typescript, astro
Kind: deep

> Fred Schott shipped Flue — a TypeScript framework that treats the agent harness as a first-class build target. Updated for the 1.0 Beta rewrite through beta.4, the new Actions primitive, @flue/react, the Cloudflare runtime deal, the Vercel-backed eve rival, and a fellow framework author's critique of what Flue makes first-class.

## TL;DR

- Flue shipped **1.0 Beta on June 16** — a **335-commit, 300-file rewrite** — and has not slowed since. **beta.3 (June 22) and beta.4 (June 23)** added **52 more commits** and a fourth top-level primitive: **Actions**. The repo is now at **~6,495 stars, 364 forks, 19 contributors**, and the tagline quietly changed to *"the sandbox agent framework."*
- The bet is unchanged: **the agent harness is the framework**, not an SDK. Logic lives in markdown skills imported as modules (`import triage from "../skills/triage/SKILL.md" with { type: "skill" }`); orchestration lives in TypeScript. beta.3 added `defineSkill()` so a skill can also be declared entirely in TypeScript for single-file agents.
- **Actions are the headline of the post-1.0 work.** A `defineAction()` is reusable, schema-validated agent logic that runs in an isolated child harness; **workflows were rebuilt on top of Actions**, and the tool API changed with them (`parameters`/`execute` became `input`/`output`/`run`). Flue also shipped **`@flue/react`** — `useFlueAgent()` and `useFlueWorkflow()` turn its durable event streams into live React state.
- The runtime story held: the **Cloudflare deal (June 17)** positions Cloudflare's Agents SDK as the *runtime layer* under harnesses. That makes the split a three-company stack — framework (Flue) over control loop (pi, now `@earendil-works/pi-agent-core`) over runtime (Cloudflare). beta.3 also canonicalized telemetry on **OpenTelemetry GenAI semantics**.
- **Flue is no longer alone.** Vercel open-sourced **eve** on June 17 — the same directory-of-files idea with Vercel's deploy gravity, now at **~2,400 stars**. Flue's answer is portability: *write once, deploy anywhere, any LLM.* shadcn's agentcn builds on both. And Alchemy author Sam Goodwin's June 19 critique still stands: the sandbox Flue makes first-class *"isn't first-class — it's simply a Layer"* — a pointed line now that Flue calls itself the sandbox framework.

![Flue's harness layered between model and runtime, shown as a four-tier stack with sandbox and filesystem at the base](/post-images/2026-05-02-flue-agent-harness-framework/hero-harness-stack.jpg)

The most interesting AI-infrastructure launch of early May did not come from a foundation lab. It came from the co-founder of a web framework. On May 1, Fred K. Schott (Astro co-founder) posted a sixteen-line pitch for a new TypeScript project called **Flue**: a framework that treats the *agent harness* as a first-class build target. Six weeks later, on June 16, Flue shipped its [1.0 Beta](https://flueframework.com/blog/flue-1-0-beta/) — and the version of Flue you can install today is a different, larger, more opinionated thing than the one that went viral in May.

This piece has been updated for that 1.0 rewrite and the betas that have come fast behind it. The thesis survived intact; almost all the code did not. So the right way to read Flue now is in two layers: the durable bet (the harness is the framework), and the June reality (a 335-commit reorganization, then `beta.3` and `beta.4` adding a fourth primitive, a React package, and a telemetry overhaul, alongside a Cloudflare runtime deal and a Vercel-backed rival that did not exist when Flue launched). The version you install today, `1.0.0-beta.4`, is days old; the framework is moving faster than most teams can adopt it, which is itself a fact worth pricing in.

# What 1.0 actually changed

The 1.0 Beta is not a version bump. The [`v0.11.1...v1.0.0-beta.1` compare](https://github.com/withastro/flue/compare/v0.11.1...v1.0.0-beta.1) is **335 commits across 300 files** — a ground-up reorganization of the public surface. Three things moved:

| Area | May launch (0.3.x) | 1.0 Beta (June) |
|---|---|---|
| Core package | `@flue/sdk/client` | `@flue/runtime` (harness, sessions, tools, sandbox) |
| Agent shape | `export default async ({ init, payload }) => {...}` | `export default defineAgent(() => ({ model, tools, skills, sandbox, instructions }))` |
| Top-level primitives | one (the agent function) | three — **Agents**, **Workflows**, **Channels** |
| Skills | discovered from disk at runtime | imported as typed modules with `import s from "./SKILL.md" with { type: "skill" }` |
| Workflow input | `payload` everywhere | `input` everywhere (SDK, CLI `--input`, RunRecord) |

The announcement frames it as a consolidation: Flue's *"core primitives (agents, workflows, sandboxes, channels) have come together into a cohesive story."* In practice it means the May post's fifteen-line example no longer compiles, and the mental model shifted from "an agent is a function you export" to "an agent is a configuration you declare." That is a meaningful upgrade, because the declarative form is what lets the same agent definition run across Node, Cloudflare, and CI without edits. But it is also a hard break six weeks after launch, which is exactly the kind of thing a `1.0-beta` label is supposed to warn you about.

# Flue in 2026, current API

The smallest current Flue agent declares a config object and returns it from `defineAgent`. From the repository README, here is the canonical bug-triage agent:

```typescript
// agents/triage.ts

import triage from '../skills/triage/SKILL.md' with { type: 'skill' };
import verify from '../skills/verify/SKILL.md' with { type: 'skill' };

const instructions = `
Triage a bug report end-to-end: reproduce the bug,
diagnose the root cause, verify whether the behavior is
intentional, and attempt a fix.
`;

// Expose (and protect) your agents over HTTP:
export const route: AgentRouteHandler = async (_c, next) => next();

export default defineAgent(() => ({
  model: 'anthropic/claude-sonnet-4-6',
  tools: [...githubTools],
  skills: [triage, verify],
  sandbox: local(),
  instructions,
}));
```

Read what the declaration carries. `model` is a string, not a configured client, so Flue resolves the provider. `tools` is a spread of typed functions. `skills` are markdown files imported as modules, so the build can typecheck that the skill exists instead of failing at runtime when a path is wrong. `sandbox: local()` wires the host filesystem; swapping it for a Cloudflare or Daytona backend is a one-line change. `route` is the HTTP guard — the seam where you add auth before the agent is reachable. Everything that was implicit in the May API (the session, the tool registration, the default sandbox) is now explicit and composable.

![Four-layer architecture diagram with model at the bottom and filesystem at the top, the harness as the load-bearing middle](/post-images/2026-05-02-flue-agent-harness-framework/four-layer-stack.jpg)

# Getting started: zero to a running agent

The canonical triage agent above is the destination. Here is the shortest path to a working agent on your own machine, taken straight from Flue's own [getting-started doc](https://flueframework.com/docs/getting-started/quickstart/) (reviewed for 1.0 Beta). You need **Node.js `>=22.19.0`** and one model provider key.

First, install the runtime and the dev CLI, drop your key in `.env`, and initialize a target. `flue init` writes a `flue.config.ts`; the `--target` flag picks where the agent will run — `node` for a local server, `cloudflare` for a Worker:

```bash
npm install @flue/runtime
npm install --save-dev @flue/cli
echo 'ANTHROPIC_API_KEY="your-api-key"' > .env
npx flue init --target node   # or: --target cloudflare
```

Add `.env` to `.gitignore` before you commit — the credentials boundary is your responsibility, not the framework's. Cloudflare's `cloudflare/*` models need no key at all, which is the cheapest way to kick the tires.

Next, the smallest possible agent. The **filename is the registration** — `agents/hello-world.ts` becomes the agent named `hello-world`, no manifest, no router entry:

```ts title="agents/hello-world.ts"

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

Then run one prompt against it. `flue run` spins up a temporary Node or Cloudflare runtime, invokes the agent through the real application path (your `app.ts` and middleware included, so what you test is what you ship), prints the result, and exits:

```bash
npx flue run hello-world --input '{"message":"Tell me a joke."}'
```

That is the whole loop: install, declare, run. The jump from here to the triage agent at the top of this piece is additive, not structural — you swap `instructions` for skills, add `tools`, set `sandbox: local()`, and bolt on a `route` guard. Every primitive in the rest of this post snaps onto the four lines above. (One caveat the [breakage section](#what-is-honestly-still-missing) earns: these commands track 1.0 Beta, and Flue has renamed core verbs twice in five weeks — pin your version and check the changelog before copying tutorials older than the current beta.)

# The harness, named

The bet that survived the rewrite is the one Flue was built to make. The word "harness" came into the lexicon in [Vivek Trivedy's LangChain post from late 2025](https://www.langchain.com/blog/the-anatomy-of-an-agent-harness), the one Fred links from the Flue homepage. Trivedy's definition is simple: *"Agent = Model + Harness. If you're not the model, you're the harness."* Everything that isn't the LLM (the loop, the tool execution, the filesystem, the sandbox, the orchestration) is the harness. Until Flue, every TypeScript framework in the space (Mastra, Vercel AI SDK, LangGraph.js, Genkit) treated those layers as **plumbing**: necessary, but assembled by the user. Flue's bet is that the harness should be **the framework**.

The architectural commitment runs deeper than the slogan. The package tells you what they actually depend on: Mario Zechner's pi agent core for the agent loop — now published as [`@earendil-works/pi-agent-core`](https://www.npmjs.com/package/@earendil-works/pi-agent-core) and `@earendil-works/pi-ai` after a mid-June namespace move off `@mariozechner/*` — [`just-bash`](https://github.com/vercel-labs/just-bash) for the in-memory virtual sandbox, [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) for MCP tool adapters, and a runtime layer that now spans Node, Cloudflare Workers, GitHub Actions, GitLab CI, Daytona, and Render. The novel contribution is the **assembly** — one orchestration shape that survives every deploy target.

[@prince_twets](https://x.com/prince_twets/status/2067620026002129285) caught the through-line in mid-June, after the rewrite landed:

> withastro/flue caught my eye today. It is a TypeScript agent framework with built in sandboxes, durable execution, and subagents. I like that it treats the harness as the product, not just the model wrapper.
>
> — [@prince_twets](https://x.com/prince_twets/status/2067620026002129285), June 18, 2026

"The harness as the product, not just the model wrapper" is the whole thesis in nine words.

# Agents, workflows, channels, actions

1.0 launched with three primitives; the betas since have made it four. They map to different shapes of work, and the distinction is the clearest articulation Flue has produced of what it is *for*.

**Agents** are autonomous. You give them instructions and tools and trust them to decide the path. **Workflows** are structured — your code guides the agent's reasoning from a defined input to a finished result, calling the model where you want judgment and keeping control where you want determinism. **Channels** are the inbound edge: verified events from Slack, Teams, Discord, and GitHub, dispatched to an agent. **Actions**, added in beta.3, are the newest and the most interesting; they get their own section below, because they quietly rewired how the other three fit together.

![A 2x2 map of Flue's four primitives — agents, workflows, channels, and actions — each drawn as a small schematic, with the newest primitive (actions) marked in editorial red](/post-images/2026-05-02-flue-agent-harness-framework/four-primitives-map.jpg)

A workflow makes the agent-as-subroutine pattern concrete. From the repository's Braintrust example:

```typescript

export const route: WorkflowRouteHandler = async (_c, next) => next();

const agent = defineAgent(() => ({ model: 'anthropic/claude-haiku-4-5' }));

const lookup = defineTool({
  name: 'lookup_weather',
  description: 'Look up current weather for a city.',
  input: v.object({ city: v.string() }),
  run: async ({ input }) => `${input.city}: sunny, 72 F`,
});

export default defineWorkflow({
  agent,
  input: v.object({ city: v.optional(v.string()) }),
  async run({ harness, input }) {
    const session = await harness.session();
    const city = input.city ?? 'San Francisco';
    const response = await session.prompt(
      `Use the weather tool to report current weather in ${city}.`,
      { tools: [lookup] },
    );
    return { message: response.text };
  },
});
```

The `run` function is plain TypeScript. It owns control flow; the model owns the reasoning inside each `session.prompt()`. Most production agent failures happen at exactly this seam, where an LLM is asked to make a routing decision it has no business making, and Flue's workflow primitive moves that seam back to the host language. A tool is a Valibot schema plus a `run` handler (the `parameters`/`execute` pair from the May API was renamed to `input`/`run` in beta.1, and Flue now validates and snapshots a declared `output` schema for you); the model can only call what you hand it in `tools`.

Subagents are first-class too. Declare a profile, attach it, and delegate with `session.task()`:

```typescript
const editor = defineAgentProfile({
  name: 'editor',
  instructions: 'Rewrite the supplied sentence in a clearer, shorter form.',
});
const agent = defineAgent(() => ({ model: 'anthropic/claude-haiku-4-5', subagents: [editor] }));
// inside run():
const response = await session.task(`Rewrite this sentence: ${draft}`, { agent: 'editor' });
```

# Actions: the primitive that rewired the rest

The most consequential change since 1.0 is not a feature on top of the existing model. It is a new noun underneath it. beta.3 introduced **Actions**, defined with `defineAction()`, and then rebuilt workflows on top of them.

An Action is reusable, schema-validated agent logic with one execution path. It declares an optional Valibot `input`, an optional `output`, and a `run({ harness, input, log })` handler that orchestrates a session:

```typescript

export const summarize = defineAction({
  name: 'summarize_document',
  description: 'Summarize a document clearly and concisely.',
  input: v.object({ text: v.string() }),
  output: v.object({ summary: v.string() }),
  async run({ harness, input }) {
    const session = await harness.session();
    const response = await session.prompt(`Summarize this text:\n\n${input.text}`);
    return { summary: response.text };
  },
});
```

The reason this matters is composition. The same Action definition has two call sites:

```typescript
// 1. Bind it to a workflow → runs as an inspectable, recorded job.
export default defineWorkflow({
  agent: defineAgent(() => ({ model: 'anthropic/claude-haiku-4-5' })),
  action: summarize,
});

// 2. Or hand it to an agent → the model decides when to call it,
//    and Flue runs it in an isolated child harness.
export default defineAgent(() => ({
  model: 'anthropic/claude-sonnet-4-6',
  instructions: 'Help the user edit and understand their documents.',
  actions: [summarize],
}));
```

When the model calls it, Flue presents the Action as a framework-managed tool and runs it in a child harness that shares the parent's sandbox, filesystem, and policy but keeps its own sessions. One definition, two call sites, with validation and snapshotting handled once.

![A single defineAction box with input, output, and run rows routed to two destinations — a workflow's inspectable recorded job on one side and an agent's isolated child harness on the other, with the action box marked in editorial red](/post-images/2026-05-02-flue-agent-harness-framework/actions-dual-callsite.jpg)

That is a real answer to the seam problem the workflow primitive was already chasing. A tool is a single function call. A skill is instructions. An Action sits in between: a multi-step, agent-backed operation you want to behave the same way every time, whether your code invokes it or the model does. The cost is conceptual surface. A builder now picks among five overlapping nouns (tool and skill, then workflow versus subagent versus Action) and the boundaries between the last three take a careful read of the docs to keep straight. The benefit is that the reliability-critical middle of an agent finally has a named home instead of living as ad-hoc code inside a `run()` closure.

The rewrite that brought Actions also renamed the declaration verbs for consistency — `createAgent()` became `defineAgent()` (with a deprecated alias), `createWorkflow()` became `defineWorkflow()` (no alias) — and bumped session persistence to version 8. The pace is the story as much as the primitive: this is a beta tearing up its own two-week-old API to get the abstraction right, again.

# Markdown logic, TypeScript orchestration

The line in the original launch that landed hardest is the one about logic location: *"Most of the 'logic' lives in Markdown: skills and context and instructions."* The 1.0 rewrite hardened this rather than softening it. Skills are no longer discovered loosely from a directory; they are imported as typed modules:

```typescript
import triage from '../skills/triage/SKILL.md' with { type: 'skill' };
```

A Flue project's structure looks like this:

```
my-agent/
├── agents/
│   └── triage.ts          # 20 lines of orchestration
├── skills/
│   └── triage/
│       └── SKILL.md       # the actual instruction set
├── tools/
│   └── github.ts          # typed tools
└── flue.config.ts
```

![A folder tree with agents/ TypeScript files small and skills/ markdown files large — the logic lives in the markdown](/post-images/2026-05-02-flue-agent-harness-framework/markdown-logic-split.jpg)

The `.ts` file is twenty-to-fifty lines: which model, which tools, which sandbox, what the agent returns. The instruction sets (the part that says what the agent should actually *do*) live in markdown. This is the same shape as Claude Code skills, Cursor rules, and Anthropic's [published skill protocol](https://www.anthropic.com/news/skills). Flue is not inventing a convention. It is taking the one that emerged from the Claude Code ecosystem and saying: this is also how you build *non-interactive* agents that ship to production. The `with { type: 'skill' }` import assertion is the upgrade — it means a missing or malformed skill is a build error, not a 2 a.m. production surprise.

beta.3 added an escape hatch that does not undercut the convention: `defineSkill()` lets you declare a skill entirely in TypeScript, with the same progressive-disclosure activation and lazy file access as a `SKILL.md` directory:

```typescript

export const review = defineSkill({
  name: 'code-review',
  description: 'Reviews code changes. Use when evaluating a patch.',
  instructions: 'Inspect the patch for correctness, then summarize risks.',
  metadata: { version: '1' },
  files: {
    'references/checklist.md': 'Check error handling, tests, and naming.',
  },
});
```

That `files` map carries the same supporting references — text or binary — a folder skill would keep on disk, loaded lazily only when the skill activates. The point is single-file agents: when you want the whole thing in one module rather than a folder tree, you no longer have to give up the skill abstraction to get there. The markdown-first default stands; the TypeScript form is for when a directory is more ceremony than the job warrants. One caveat that landed in the same release: skill names must now follow the spec's ASCII rules (1–64 lowercase letters, numbers, single hyphens), so any Unicode-named skills from the May era need renaming before you upgrade.

# The Cloudflare deal is the real June story

The 1.0 announcement got the GitHub stars. The more consequential June event was [Cloudflare's June 17 post](https://blog.cloudflare.com/agents-platform-flue-sdk/) positioning its Agents SDK as the *runtime layer* underneath harnesses — and naming Flue as the first. Cloudflare's framing is blunt about why now:

> 2026 is the year agent harnesses go to production. The software that controls the model's access to the outside world — harnesses like Codex, Claude Code, OpenCode, Pi, and Project Think — has matured to the point where teams are deploying agents as real, load-bearing infrastructure.
>
> — Thomas Gauvin, [Cloudflare](https://blog.cloudflare.com/agents-platform-flue-sdk/), June 17, 2026

This separates the stack into three layers owned by three different parties: the **framework** (Flue, where you write the agent), the **control loop** (Pi, the agent core Flue is built on), and the **runtime** (Cloudflare Agents SDK + Durable Objects, where it actually executes and persists). [@stretchcloud](https://x.com/stretchcloud/status/2067346544509235711) read the move correctly:

> Every serious agent demo eventually hits the boring failure mode … the "AI" problem quietly becomes a distributed systems problem. That is why this Cloudflare move is more interesting than another framework launch. They are trying to make the Agents SDK the runtime layer under different harnesses.
>
> — [@stretchcloud](https://x.com/stretchcloud/status/2067346544509235711), June 17, 2026

That is the durability gap from Flue's launch week, the *"is stream reconnect on the roadmap?"* question, being answered not by Flue alone but by pushing the hard distributed-systems problem down to a runtime built for it. Durable Objects already solve session persistence across requests; leaning on them is the pragmatic answer to "what happens when the process dies mid-task."

![A three-layer stack diagram: framework on top, control loop in the middle, runtime at the base, with the runtime layer marked in editorial red](/post-images/2026-05-02-flue-agent-harness-framework/runtime-layer-stack.jpg)

# Same agent code, multiple backends

The thing reviewers kept noticing on launch night was the sandbox abstraction, and it survived the rewrite as `@flue/runtime` backends. From [@curonianai](https://x.com/curonianai/status/2050307603637416077): *"sandbox as primitive, not adapter. Mastra and AI SDK make you BYO the sandbox. Same agent code, multiple backends. Filesystem composition becomes native, which is the actual lever."*

| Sandbox | Implementation | Use case |
|---|---|---|
| `virtual` (default) | `just-bash` in-memory bash + filesystem | Stateless agents, high-traffic webhooks, no container overhead |
| `local()` | Host filesystem | CI runners with the repo already checked out |
| Daytona | `@daytona/sdk` Linux container | Full Linux env, persistent filesystem, real shell |
| Cloudflare Sandbox | `@cloudflare/sandbox` Durable Object container | Per-session container on Cloudflare with persistence |

The agent declaration does not change when the backend does. The default (`virtual`) is the lever Mastra and Vercel AI SDK do not give you: most agent work needs a filesystem, not a full container, and spinning up a container per webhook destroys agent unit economics. Flue makes the cheap path the default and the expensive path opt-in.

# @flue/react closes the loop to the frontend

For its first six weeks Flue stopped at the server. You wrote the harness, exposed it over HTTP, and built whatever UI you wanted on top of `@flue/sdk`. The 1.0 line added a first-party answer for the most common one: **`@flue/react`**, a package that turns Flue's durable event streams into live React state.

The surface is two hooks. `useFlueAgent()` gives you a continuing conversation with an agent instance; `useFlueWorkflow()` observes a finite workflow run. A chat UI is about a dozen lines:

```tsx

export function Chat({ conversationId }: { conversationId: string }) {
  const agent = useFlueAgent({ name: 'support-assistant', id: conversationId });
  return (
    
  );
}
```

`useFlueAgent` reconstructs the transcript from the durable event log (the latest 100 events by default; pass `history: 'all'` for the full thread), then follows new events live. Both hooks subscribe to the same persisted stream the runtime already keeps, so the UI reads the authoritative record rather than a best-effort socket. The message shape is parts-based — text, reasoning, tool activity, images — and deliberately mirrors AI SDK v5's `UIMessage` without depending on `ai` at runtime or implementing its transport. `sendMessage()` resolves when the server *admits* the prompt, not when generation finishes, and the stream then reconciles that optimistic message with its durable copy. beta.4's release note is a tell about how carefully this seam is built: the hook *"keeps optimistic messages in their canonical transcript position when durable echoes arrive."* That is the language of someone who has been bitten by chat UIs that double-render a message when the optimistic update and the server echo disagree. Transport defaults to Durable Streams long-polling; pass `live: 'sse'` for a single SSE connection instead.

This is a meaningful widening of scope. With React in the box, the same durability machinery that survives a dropped server process now also drives a chat transcript that survives a page reload, because both read the one event log. It also sharpens the contrast with eve, whose pitch leans on Vercel's frontend gravity: Flue is reaching for the frontend too, but on the write-once-deploy-anywhere terms it set for everything else.

![A React chat UI on the left reading from a durable event log on the right over long-poll or SSE, with an optimistic message reconciling into its durable copy and the authoritative message_end event marked in editorial red](/post-images/2026-05-02-flue-agent-harness-framework/react-durable-stream.jpg)

# Where Flue actually came from

Frameworks invented from first principles tend to look like it — answers to questions nobody is asking, gaps where the real work is. Flue does not have that smell, and the reason is in the git history.

On February 11, 2026, [Fred opened PR #15476](https://github.com/withastro/astro/pull/15476) on the Astro monorepo titled *"Experiment: Automatic triage for new GitHub issues."* It was in-house infrastructure: the Astro repo gets a steady stream of issues, the maintainers cannot triage them all, and a Claude-Code-style agent could do the first pass. The PR added 14 files, +831 lines. Two weeks later, [PR #15545](https://github.com/withastro/astro/pull/15545) was 19 files of *"security improvements, sandbox updates, skill refinements"* — exactly the unglamorous edges that only show up after the thing is running on real traffic.

By March those skills had been generalized far enough that they no longer looked like Astro-specific infrastructure. Mario Zechner (author of the underlying `pi-agent-core` library) got his hands on it, the response was *"holy shit it's built on pi?!"*, and Fred extracted the abstraction:

> ok, i already posted this but holy shit it's built on pi?! … this makes me super happy!
>
> — [@badlogicgames](https://x.com/badlogicgames/status/2050288716896247887), May 1, 2026

This origin rules out the most common failure mode in the category: a clean abstraction nobody used in production. Flue's harness ran on the Astro repo's issue triage for two months before it had a name. Every primitive was added because the maintainers needed it to ship one specific feature. That the 1.0 rewrite then reorganized those primitives is not a contradiction — it is what happens when a tool built for one repo gets generalized for everyone, and the original shape turns out to be too narrow.

# The rival that did not exist in May: Vercel's eve

The biggest change to Flue's world is not in Flue's repo. On June 17, at Vercel Ship London, [Vercel open-sourced **eve**](https://vercel.com/blog/introducing-eve) — its own agent framework, built on the same core idea that an agent is a directory of files. The framing is nearly identical to Flue's: markdown instructions, typed TypeScript tools, skills, sandboxes, durable workflows. Vercel pitches it as *"Next.js, for agents."*

The two frameworks now define the poles of the TypeScript agent-framework debate:

| | **Flue** | **eve** (Vercel) |
|---|---|---|
| Backer | Astro team (Fred Schott) | Vercel (Guillermo Rauch) |
| Core pitch | Write once, deploy **anywhere**, any LLM | Scaffold to running agent in under a minute |
| Deploy gravity | Node, Cloudflare, CI, Daytona, Render | Vercel Functions + Sandbox (portable, but Vercel-native) |
| Maturity (June 23) | 1.0 Beta (beta.4 shipped), ~6,495 stars, 4 months old | 0.11.x, ~2,400 stars, 6 days old |
| LLM routing | Provider-agnostic strings | AI Gateway (OIDC, no keys on Vercel) |

The honest contrast is **portability versus gravity**. Flue's whole value proposition is that your agent code does not care where it runs; eve's is that on Vercel, the deploy story disappears entirely (OIDC means no API keys, the sandbox and workflow engine are wired in). Reviewers have already flagged the tradeoff — eve's speed-to-first-agent comes with deploy-gravity that becomes a portability bill later, which is precisely the bill Flue is built to never charge.

They are not purely adversarial. shadcn's **agentcn**, launched June 20, builds on both:

> Introducing agentcn 🤖 by @shadcnlabs — Built on Eve by @vercel and @flueai — Zero config, one command setup. @shadcn/ui compatible … 10+ production-ready agent recipes.
>
> — [@alaymanguy](https://x.com/alaymanguy/status/2068382433599439282), June 20, 2026

When the most influential component-library author in the React ecosystem treats two competing frameworks as interchangeable backends, it tells you the *category* has won even before either framework has. The harness-as-framework idea is now the consensus shape; Flue and eve are arguing over who hosts it. (We cover eve's architecture in depth in [the companion piece on Vercel's framework](/posts/2026-06-21-vercel-eve-agent-framework-directory-of-files/).)

# The contrarian read: is the sandbox the right primitive?

![Two contrasting architecture diagrams: Flue making the sandbox first-class inside the harness, versus Alchemy making the agent-as-typed-interface the primitive with the sandbox as one swappable layer, the agent-interface box marked in red](/post-images/2026-05-02-flue-agent-harness-framework/which-primitive-debate.jpg)

Not everyone agrees the harness-as-framework framing is the unit that matters. The sharpest pushback came not from a foundation lab but from a fellow framework author. Sam Goodwin, who builds the infrastructure-as-code framework Alchemy, looked at Flue's design and argued that Flue picked the wrong thing to make first-class:

> Alchemy.Agent is a type-safe string template capturing Prompt and Tools together. It's the "interface". Cloudflare.Agent() implements it with a DO. The "Sandbox" feature of Flue is simply a Layer. It isn't first-class.
>
> — [@samgoodwin89](https://x.com/samgoodwin89/status/2067868213069918404), June 19, 2026

The critique is worth taking seriously because it inverts Flue's own marketing. Flue sells the sandbox as a framework-level primitive, the thing Mastra and the Vercel AI SDK leave to the user. Goodwin's claim is the opposite: the *agent interface*, prompt and tools captured as a type, is the real primitive, and the sandbox is just one runtime layer you compose underneath it, no more privileged than a database binding. Read that way, Flue and Alchemy disagree about which abstraction deserves to be the noun. Flue says the harness; Alchemy says the agent-as-typed-interface, with the runtime (a Cloudflare Durable Object, a sandbox, whatever) as a swappable implementation detail. This is not a small quibble. It is the same argument the frontend world had about whether the *component* or the *route* is the primitive, and it will shape which of these frameworks composes cleanly into larger systems. Flue's answer is defensible — for an agent that writes files, the sandbox genuinely is load-bearing — but the fact that a serious infra author looked at the design and reached for a different noun is the clearest sign the category's foundations are still being argued, not settled.

The disagreement got pointed since: at some point in the post-1.0 churn, Flue's own repo tagline changed from "the agent harness framework" to *"the sandbox agent framework."* Flue is leaning *harder* into the exact noun Goodwin says is only a layer. And yet the same betas that doubled down on the sandbox in the headline also shipped Actions — a primitive defined precisely as schema-typed `input`, `output`, and a handler, which is a long step toward the typed-interface-as-primitive idea Goodwin was arguing for. The honest read is that Flue is hedging in code what it is asserting in marketing: the sandbox stays the marquee noun, but the type-first interface keeps gaining surface underneath it.

# Mapping Flue against the rest of the TypeScript agent stack

The category has gotten crowded fast. Six frameworks worth comparing on the dimensions Flue actually cares about:

| Framework | Sandbox primitive? | Markdown-first? | Deploy targets | Secrets boundary |
|---|---|---|---|---|
| **Flue** (`@flue/runtime`) | First-class (4 backends) | Yes (skills as typed modules) | Node, Cloudflare, GitHub Actions, GitLab CI, Daytona, Render | Per-call command grants |
| **eve** (Vercel) | First-class (Vercel Sandbox) | Yes (instructions + skills) | Vercel-native (portable) | Runtime OAuth (Connect) |
| **Mastra** | BYO container | Workflows-as-code | Node, Cloudflare, hosted | Tool-level config |
| **Vercel AI SDK** | None | No | Vercel, Edge | Tool-level config |
| **LangGraph.js** | BYO | No | Node, hosted | Per-tool |
| **`@earendil-works/pi-agent-core`** (Flue dependency) | BYO | TUI/CLI focused | Node | Per-tool |

Two columns matter most. The "sandbox primitive?" column is what `@curonianai` flagged: Flue ships sandbox backends as part of the framework contract, where Mastra and Vercel AI SDK leave it to the user — the right call for a chat product, the wrong call for an agent that writes files. The "markdown-first?" column is the cultural commitment to skill files as the unit of agent capability, the same skill working in Claude Code, in Cursor, and in production without translation. The arrival of eve in the first two rows is the headline: the dimensions Flue chose to compete on are now the dimensions the whole category is converging toward.

# What you can build with Flue right now

Ten things that are plausible weekend projects given 1.0's primitives, ranked by how cleanly the framework's pieces snap together.

| Buildability | Idea | Why it fits Flue |
|---|---|---|
| 🟢 High | **GitHub issue triage bot** | Flue's own origin story. A `triage` skill + the GitHub channel + `local()` sandbox is the entire setup. Replaces Dosu / Greptile / CodeRabbit at near-zero infrastructure cost. |
| 🟢 High | **PR review agent** | Same shape, different skill. Channel fires on `pull_request_review_comment`, agent runs `git diff`, posts review comments. |
| 🟢 High | **Customer support agent with persistent sessions** | Cloudflare backend, R2-backed knowledge base mounted as the agent's filesystem, Durable Object sessions for continuity across messages. |
| 🟢 High | **CI auto-fix workflow** | A `defineWorkflow` branching on a `triage` result severity into an `auto-fix` skill. Best at high-confidence dependency bumps and lint fixes. |
| 🟢 High | **Docs Q&A agent** | R2 bucket of markdown docs, virtual sandbox, `grep`/`glob`/`read` over the bucket as filesystem. No vector DB needed for small corpora. |
| 🟡 Medium | **Slack/Discord/Teams agent** | The channel primitive verifies inbound events; the agent itself is twenty lines. Signed-request verification is handled by the channel, not your code. |
| 🟡 Medium | **Code-review concierge for a private repo** | Daytona backend gives a real container with the repo cloned. Coordination across PRs is the hard part. |
| 🟡 Medium | **Data-analyst agent** with mounted CSVs | Virtual sandbox with Python enabled, mount a directory of datasets, ask questions. Charts via matplotlib written back to the filesystem. |
| 🟡 Medium | **Multi-agent research pipeline** | A parent agent delegating to declared subagents via `session.task()`, each a focused profile. |
| 🔴 Low | **Mid-stream-resumable hour-long agent** | The Cloudflare runtime deal addresses session persistence, but mid-`prompt()` stream resumption across a dropped connection is still the frontier. |

The pattern across the green tier is consistent: a channel or CI trigger, a virtual or local sandbox, a markdown skill, scoped secrets, a typed result. Twenty lines of TypeScript and a fifty-line markdown file. That is the shape of a Flue agent at its most useful, and 1.0's job was to make that shape feel obvious across more runtimes.

![Build-idea cards arranged as a grid of agent shapes, with the issue-triage card highlighted in editorial red as the canonical example](/post-images/2026-05-02-flue-agent-harness-framework/build-ideas-grid.jpg)

# What is honestly still missing

Four real gaps, named by name.

**It is a beta, and the breakage has not stopped.** The `1.0.0-beta` label is doing real work. The June 16 rewrite renamed core APIs (`createAgent` → `defineAgent`, `payload` → `input`) and bumped session persistence to a new on-disk format. Then beta.3 did it *again*: tools moved to `input`/`output`/`run`, workflows were rebuilt on Actions, session persistence jumped to version 8 (version 6 data is unsupported and must be cleared or migrated), channel handlers switched to provider-native payloads, and the public `/openapi.json` route was removed. Anyone who shipped on the May API has now migrated twice in five weeks. The announcement is explicit that this is the cohesive-but-not-frozen moment, not GA — more breaking changes are possible before 1.0 final.

**The velocity itself is a risk.** This is the flip side of all the progress above. A framework that adds a fourth primitive, a frontend package, and a telemetry overhaul across two same-week betas is a framework whose documentation, examples, and community tutorials are perpetually a version behind. Several of Flue's own docs pages still carry an "AI-generated, awaiting review" banner. For a team evaluating Flue today, the gap to price in is not a missing feature; it is that the thing you learn this week may be renamed next week. That is the tax of building on the bleeding edge, and Flue is squarely on it.

**Stream reconnect is still partial.** The Cloudflare deal pushes session durability down to Durable Objects, which covers state across requests, and `@flue/react` now hydrates a transcript from the exact durable checkpoint. But resuming a single in-flight `prompt()` whose HTTP stream drops mid-generation remains a per-runtime concern rather than a framework primitive. For research and coding agents that run for minutes per call, this is the edge users will hit.

**TypeScript only.** The Python agent ecosystem (LangChain, LlamaIndex, CrewAI, and AutoGen, among others) is enormous and not coming. Flue's bet is that the developer pool willing to write agents in TypeScript is the one growing fastest. With Vercel now backing eve in the same language, that bet looks better than it did in May — but it is still definitionally not the AI-research crowd.

# Where Flue lands

Agent harnesses are the unglamorous middle of the AI stack. The model gets the headline, the deploy target gets the press release, and the harness (the loop, the sandbox, the filesystem, the secrets boundary, the orchestration) is the thing every team rebuilds from scratch and nobody calls a framework.

Flue named the harness as a build target first. The May launch proved the idea resonated; the June 1.0 rewrite proved the team would tear up its own API to get the shape right; the betas since proved it would do it again within the week — adding Actions, a React package, and an OpenTelemetry overhaul on top of a still-unfrozen core; the Cloudflare deal proved a major infrastructure company believes the harness/runtime split is real enough to build a product around; and Vercel's eve proved the category is big enough to attract the most distribution-heavy player in frontend. Four months in, the question is no longer *"is the harness a real category?"* It is *"which framework hosts it, and on whose runtime?"*

For builders, the call has actually gotten easier since May. The framework is more capable, the runtime story is more credible, and the existence of a serious rival means the patterns are converging fast enough that learning one teaches you most of the other. The thing you give up by waiting is the same as it was at launch — the chance to be one of the first hundred teams who ship on it, learn its shape, and end up writing the patterns the next ten thousand will copy. The harness moment is no longer latent. Two frameworks and a CDN are now fighting over it in public. The right move is still to build something on it.

## Sources

- [Flue 1.0 Beta announcement](https://flueframework.com/blog/flue-1-0-beta/)
- [withastro/flue (GitHub)](https://github.com/withastro/flue)
- [flueframework.com](https://www.flueframework.com)
- [Flue 1.0 rewrite — v0.11.1...v1.0.0-beta.1 compare (335 commits)](https://github.com/withastro/flue/compare/v0.11.1...v1.0.0-beta.1)
- [Cloudflare — Agents platform + the Flue SDK](https://blog.cloudflare.com/agents-platform-flue-sdk/)
- [LangChain — The Anatomy of an Agent Harness](https://www.langchain.com/blog/the-anatomy-of-an-agent-harness)
- [@flue/sdk on npm](https://www.npmjs.com/package/@flue/sdk)
- [@flue/cli on npm](https://www.npmjs.com/package/@flue/cli)
- [pi-agent-core, now @earendil-works (npm)](https://www.npmjs.com/package/@earendil-works/pi-agent-core)
- [Astro PR #15476 — Auto-triage experiment (origin)](https://github.com/withastro/astro/pull/15476)
- [Astro PR #15545 — Triage security + sandbox refinements](https://github.com/withastro/astro/pull/15545)
- [Vercel — Introducing eve (the rival, June 17)](https://vercel.com/blog/introducing-eve)
- [just-bash (vercel-labs)](https://github.com/vercel-labs/just-bash)
- [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/)
- [Mastra (TypeScript agent framework)](https://mastra.ai/)
- [Vercel AI SDK](https://ai-sdk.dev/)
- [@stretchcloud — Cloudflare makes the runtime the layer](https://x.com/stretchcloud/status/2067346544509235711)
- [@prince_twets — the harness is the product](https://x.com/prince_twets/status/2067620026002129285)
- [@badlogicgames — built on pi](https://x.com/badlogicgames/status/2050288716896247887)
- [@curonianai — sandbox as primitive](https://x.com/curonianai/status/2050307603637416077)
- [@alaymanguy — agentcn, built on Eve and Flue](https://x.com/alaymanguy/status/2068382433599439282)
- [@samgoodwin89 — the sandbox isn't first-class (Alchemy author critique)](https://x.com/samgoodwin89/status/2067868213069918404)
- [Flue beta.1...beta.2 compare (25 commits, Astro 7)](https://github.com/withastro/flue/compare/v1.0.0-beta.1...v1.0.0-beta.2)
- [Flue beta.2...beta.4 compare (52 commits — Actions, evals, schedules)](https://github.com/withastro/flue/compare/v1.0.0-beta.2...v1.0.0-beta.4)
- [Flue docs — Actions (the new primitive, defineAction)](https://flueframework.com/docs/guide/actions/)
- [Flue docs — React (@flue/react, useFlueAgent / useFlueWorkflow)](https://flueframework.com/docs/guide/react/)
- [Flue CHANGELOG (beta.1 through beta.4)](https://github.com/withastro/flue/blob/main/CHANGELOG.md)
- [The Road to Enterprise — Eve vs Flue: Which TypeScript Agent Framework?](https://theroadtoenterprise.com/blog/eve-vs-flue-typescript-agent-framework)

---

Canonical: https://www.thedeepfeed.ai/posts/2026-05-02-flue-agent-harness-framework/
Site: https://www.thedeepfeed.ai
Full corpus: https://www.thedeepfeed.ai/llms-full.txt