Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- Agent and workflow WebSocket frames reject blank or whitespace-only `requestId` values, including optional agent ping IDs.
- Published the Message-Driven Agents guide, Sandbox Connector API, and Daytona integration guide on the documentation site. Replace saved root-guide or raw GitHub links with [Message-Driven Agents](https://flueframework.com/docs/guide/message-driven-agents/), [Sandbox Connector API](https://flueframework.com/docs/api/sandbox-api/), and [Daytona](https://flueframework.com/docs/ecosystem/sandboxes/daytona/).
- Refreshed homepage and documentation canonical URLs and social-preview metadata.
- **Cloudflare: Extend generated deployments and addressable agents.** Add an optional source-root `cloudflare.ts` module to export application-owned Durable Objects and compose non-HTTP Worker handlers. Addressable agent modules may export `cloudflare = extend({ base, wrap })` from `@flue/runtime/cloudflare` to add native Agents SDK lifecycle hooks beneath Flue-owned routing or wrap the final generated Durable Object class with integrations such as Sentry.
- **Cloudflare Sandbox exports are now explicit.** Export Cloudflare Sandbox aliases from your source-root `cloudflare.ts` module instead of relying on the removed `Sandbox`-suffix auto-wiring.

## 0.9.0 - 2026-06-02

Expand Down
108 changes: 102 additions & 6 deletions apps/docs/src/content/docs/ecosystem/deploy/cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,87 @@ chat.close();

An exported `websocket` middleware can authenticate its own agent or workflow socket endpoint. Custom `.flue/app.ts` applications provide centralized authentication and mounted prefixes: for example, apply `app.use('/api/agents/*', authenticate)` and `app.use('/api/workflows/*', authenticate)` before `app.route('/api', flue())` to cover both socket surfaces before Flue forwards accepted upgrades into their owning Durable Objects. SDK clients can connect through that mount with `baseUrl: 'https://example.com/api'` and attach query-token or signed handshake context with `websocketUrl: (url) => { url.searchParams.set('token', socketToken); return url; }`. HTTP `token` and `headers` options do not automatically apply to WebSocket upgrades; browsers should use cookies or application-designed URL authentication, while Node clients requiring implementation-specific headers can provide a custom `websocket` factory. Cloudflare socket authentication is established during the handshake: query parameters and original upgrade headers are not restored into operation-time request context after Durable Object forwarding. Avoid header-mutating middleware such as CORS wrapping WebSocket upgrade routes, because WebSocket upgrade responses may have immutable headers.

### Extending an addressable Cloudflare agent

Flue normally owns each generated agent Durable Object class. When an addressable agent needs native Cloudflare Agents SDK capabilities such as `onStart()`, `schedule()`, `scheduleEvery()`, or `queue()`, export a `cloudflare` extension descriptor from its module:

```ts
import { createAgent } from '@flue/runtime';
import { extend } from '@flue/runtime/cloudflare';

export default createAgent(() => ({ model: 'anthropic/claude-sonnet-4-6' }));

export const cloudflare = extend({
base: (Base) =>
class extends Base {
async onStart() {
await this.scheduleEvery(60, 'heartbeat');
}

async heartbeat() {
this.setState({ ...this.state, lastHeartbeatAt: Date.now() });
}
},
});
```

This is an advanced Cloudflare-only extension point. Flue applies `base` first, then defines its own Durable Object subclass while preserving the filename-derived binding and migration name. Use `base` for native SDK lifecycle hooks and additional named methods. Do not override `fetch()`, `onRequest()`, WebSocket hooks, `onFiberRecovered()`, or `alarm()`: Flue and the Agents SDK use those methods for routing, hibernating connections, interruption recovery, and alarm multiplexing.

Use `wrap` when an integration needs to wrap the final Flue-generated Durable Object class:

```ts
import * as Sentry from '@sentry/cloudflare';

export const cloudflare = extend({
wrap: (Final) =>
Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({ dsn: env.SENTRY_DSN }),
Final,
),
});
```

Both `base` and `wrap` are optional. This agent-module export is distinct from the optional source-root `.flue/cloudflare.ts` deployment module below. Native SDK callbacks run as agent-instance activity: they do not receive a Flue workflow context, create workflow runs, or automatically initialize a Flue harness or session.

### Extending the Worker

Add an optional `.flue/cloudflare.ts` module when your deployment needs native Cloudflare capabilities outside Flue's generated classes. Named exports become top-level Worker exports, which lets the same Worker define application-owned Durable Objects:

```ts
import { DurableObject } from 'cloudflare:workers';

export class SalesforceAuthCache extends DurableObject {
async refreshIfNeeded() {
return await this.ctx.storage.get('token');
}
}
```

Declare the corresponding binding and migration in your project-root `wrangler.jsonc`:

```jsonc
{
"durable_objects": {
"bindings": [{ "name": "SALESFORCE_AUTH_CACHE", "class_name": "SalesforceAuthCache" }],
},
"migrations": [{ "tag": "v2", "new_sqlite_classes": ["SalesforceAuthCache"] }],
}
```

Your agents and workflows receive the namespace through `env.SALESFORCE_AUTH_CACHE`. Keep bindings, containers, and ordered migration history in Wrangler configuration; `cloudflare.ts` provides the Worker code exports but does not infer deployment topology.

An optional default export adds non-HTTP Worker handlers:

```ts
export default {
async scheduled(_controller, env) {
await env.SALESFORCE_AUTH_CACHE.getByName('default').refreshIfNeeded();
},
};
```

Use `.flue/app.ts` for custom HTTP routes and middleware. `cloudflare.ts` must not export a default `fetch` handler because Flue keeps HTTP composition in `app.ts`.

## Subagents

Subagents define named delegates for detached task sessions:
Expand Down Expand Up @@ -248,16 +329,23 @@ If you'd rather connect to an external provider — e.g. Daytona — instead of

### Setup

You own the container config. That means three things:
You own the container config. That means four things:

1. Install `@cloudflare/sandbox`: `npm install @cloudflare/sandbox`.
2. Declare the Durable Object binding, migration, and container image in your `wrangler.jsonc` at the project root.
3. Commit a `Dockerfile` at the path your `containers[].image` points to.
2. Export the Sandbox class from `.flue/cloudflare.ts`.
3. Declare the Durable Object binding, migration, and container image in your `wrangler.jsonc` at the project root.
4. Commit a `Dockerfile` at the path your `containers[].image` points to.

Flue automates one piece: **any DO binding whose `class_name` ends with `Sandbox` is automatically wired up as `@cloudflare/sandbox`'s `Sandbox` class in the generated Worker bundle.** Pick any name you want (`Sandbox`, `PyBoxSandbox`, `SupportSandbox`, …) and Flue handles the re-export. Append its migration to the same top-level history you use for generated Flue classes; do not replace migrations that have already been deployed.
Append the Sandbox migration to the same top-level history you use for generated Flue classes; do not replace migrations that have already been deployed.

### Example

`.flue/cloudflare.ts`:

```ts
export { Sandbox } from '@cloudflare/sandbox';
```

`wrangler.jsonc` (at the project root, alongside `package.json`):

```jsonc
Expand Down Expand Up @@ -301,7 +389,13 @@ export default createAgent(({ id, env }) => ({

### Multiple sandboxes

Different agents can use different container images. Declare a separate binding for each (each `class_name` must end with `Sandbox`), and give each its own container entry:
Different agents can use different container images. Export a separate alias for each Sandbox class, then declare each binding and container entry:

```ts
// .flue/cloudflare.ts
export { Sandbox as PyBoxSandbox } from '@cloudflare/sandbox';
export { Sandbox as NodeSandbox } from '@cloudflare/sandbox';
```

```jsonc
{
Expand Down Expand Up @@ -331,7 +425,9 @@ When your agent runs in a container, it may need to call external APIs — GitHu
Cloudflare Sandboxes solve this with [outbound Workers](https://blog.cloudflare.com/sandbox-auth/) — a programmable egress proxy that intercepts outgoing HTTP/HTTPS requests from the container. Secrets are injected at the proxy layer, so the container never sees them. This is configured on the Cloudflare Sandbox class, outside of your Flue agent code:

```typescript
class MySandbox extends Sandbox {
import { Sandbox } from '@cloudflare/sandbox';

export class MySandbox extends Sandbox {
static outboundByHost = {
'api.github.com': (request, env, ctx) => {
const headers = new Headers(request.headers);
Expand Down
9 changes: 8 additions & 1 deletion apps/docs/src/content/docs/ecosystem/sandboxes/cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ Cloudflare Sandbox uses `@cloudflare/sandbox` to provide a container-backed Linu

## Use the Cloudflare target

Cloudflare Sandbox requires a Worker deployment, Durable Object/container configuration, and a container image. Add the dependency to a Cloudflare-targeted project, declare the sandbox binding in Wrangler configuration, and pass the RPC stub returned by `getSandbox(...)` to an agent:
Cloudflare Sandbox requires a Worker deployment, Durable Object/container configuration, and a container image. Add the dependency to a Cloudflare-targeted project and export its Durable Object class from your Cloudflare deployment module:

```ts
// <source-root>/cloudflare.ts
export { Sandbox } from '@cloudflare/sandbox';
```

Declare the sandbox binding in Wrangler configuration, then pass the RPC stub returned by `getSandbox(...)` to an agent:

```ts
import { getSandbox } from '@cloudflare/sandbox';
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/src/content/docs/guide/develop-and-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This guide covers that lifecycle. For source files and discovery conventions, se

## Develop

`flue dev` is the local development server for a Flue application. It builds the discovered agents, workflows, and optional `app.ts`, serves the application locally, and rebuilds as source files change.
`flue dev` is the local development server for a Flue application. It builds the discovered agents, workflows, optional `app.ts`, and optional Cloudflare-only `cloudflare.ts`, serves the application locally, and rebuilds as source files change.

After selecting your normal runtime target in `flue.config.ts`, start the development server:

Expand Down Expand Up @@ -54,7 +54,7 @@ pnpm exec flue build

The build uses that configured target, or a one-time `--target` override, to produce deployable output in `dist/` by default. See [Configuration](/docs/reference/configuration/) to change the output directory.

A build packages the application for its runtime environment. It does not choose a model, add provider credentials, expose additional routes, or configure platform-owned bindings. Keep those concerns in your authored application modules, secrets configuration, and deployment-platform configuration.
A build packages the application for its runtime environment. It does not choose a model, add provider credentials, expose additional routes, or configure platform-owned bindings. Keep those concerns in your authored application modules, secrets configuration, and deployment-platform configuration. Cloudflare applications may add platform-specific Worker exports and non-HTTP handlers in `cloudflare.ts`; see [Deploy on Cloudflare](/docs/ecosystem/deploy/cloudflare/#extending-the-worker).

## Deploy

Expand Down
16 changes: 12 additions & 4 deletions apps/docs/src/content/docs/guide/project-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: Understand the source files and generated output in a Flue project.
lastReviewedAt: 2026-05-29
---

Flue discovers application entrypoints from your project's source directory. Use `src/` for new projects, with `app.ts`, `agents/`, and `workflows/` defining the application surfaces Flue builds.
Flue discovers application entrypoints from your project's source directory. Use `src/` for new projects, with `app.ts`, `cloudflare.ts`, `agents/`, and `workflows/` defining the application surfaces Flue builds.

## Example project layout

Expand All @@ -14,6 +14,7 @@ my-project/
├─ flue.config.ts
├─ src/
│ ├─ app.ts
│ ├─ cloudflare.ts
│ ├─ agents/
│ │ └─ support-assistant.ts
│ └─ workflows/
Expand All @@ -27,8 +28,9 @@ Organize supporting application code however you prefer inside `src/`. The files

| Path | Purpose | Learn more |
| ------------ | ------------------------------------------------------------------------------------- | -------------------------------------- |
| `app.ts` | Optional entrypoint for composing Flue with your application's routes and middleware. | [Routing](/docs/guide/routing/) |
| `agents/` | Addressable agents that can receive continuing interactions over time. | [Agents](/docs/guide/building-agents/) |
| `app.ts` | Optional entrypoint for composing Flue with your application's routes and middleware. | [Routing](/docs/guide/routing/) |
| `cloudflare.ts` | Optional Cloudflare-only module for Worker exports and non-HTTP handlers. | [Cloudflare](/docs/ecosystem/deploy/cloudflare/#extending-the-worker) |
| `agents/` | Addressable agents that can receive continuing interactions over time. | [Agents](/docs/guide/building-agents/) |
| `workflows/` | Finite operations that receive input and return a result. | [Workflows](/docs/guide/workflows/) |

### `app.ts`
Expand All @@ -37,6 +39,12 @@ Organize supporting application code however you prefer inside `src/`. The files

For more information, see [Routing](/docs/guide/routing/).

### `cloudflare.ts`

`cloudflare.ts` is an optional Cloudflare-only deployment module. Its named exports become top-level Worker exports, and its optional default export adds non-HTTP Worker handlers. Use it for same-Worker Durable Object classes, explicit Cloudflare Sandbox aliases, queue consumers, scheduled handlers, and other Cloudflare-native additions. Custom HTTP handling remains in `app.ts`.

For more information, see [Deploy on Cloudflare](/docs/ecosystem/deploy/cloudflare/#extending-the-worker).

### `agents/`

The `agents/` directory contains agents that Flue can address by name. Each immediate file defines one discovered agent, and its filename becomes the agent name: `src/agents/support-assistant.ts` is discovered as `support-assistant`.
Expand All @@ -61,7 +69,7 @@ For more information, see [Workflows](/docs/guide/workflows/).
2. `src/` **(Recommended)** — The recommended layout for new projects.
3. The project root — A compact layout for small dedicated projects.

The first matching directory wins. Flue does not merge layouts: when `.flue/` exists, it does not discover agents, workflows, or `app.ts` from `src/` or the project root. Authored modules may still import ordinary supporting code from elsewhere in the project.
The first matching directory wins. Flue does not merge layouts: when `.flue/` exists, it does not discover agents, workflows, `app.ts`, or `cloudflare.ts` from `src/` or the project root. Authored modules may still import ordinary supporting code from elsewhere in the project.

The source directory is always discovered relative to your project root. To configure the project root, see [Configuration](/docs/reference/configuration/).

Expand Down
28 changes: 18 additions & 10 deletions connectors/sandbox--cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ Node.js process. Because of that, Flue treats Cloudflare Sandbox as a
first-class **build target**, not a drop-in connector file.

If the user is already on `--target cloudflare`: there is no connector to
install. Flue's runtime package already provides the wiring; you just declare the
binding in `wrangler.jsonc` and call `getSandbox(env.Sandbox, id)` in the
agent. Skip to ["Path A"](#path-a-already-on---target-cloudflare) below.
install. Export the Sandbox class from the selected Flue source root's
`cloudflare.ts`, declare the binding in `wrangler.jsonc`, and call
`getSandbox(env.Sandbox, id)` in the agent. Skip to
["Path A"](#path-a-already-on---target-cloudflare) below.

If the user is on `--target node` (or hasn't picked yet): adding Cloudflare
Sandbox means **migrating the entire project to deploy on Cloudflare
Expand Down Expand Up @@ -77,10 +78,17 @@ The short version, for your reference:

(Use the user's package manager — `pnpm add`, `yarn add`, etc.)

2. Add a Durable Object binding for the sandbox to the user's
`wrangler.jsonc` (at the project root). **The `class_name` must end
with `Sandbox`** — Flue's build step auto-wires any DO whose class name
ends in `Sandbox` to `@cloudflare/sandbox`'s `Sandbox` class:
2. Export the Sandbox class from the user's Cloudflare deployment module.
Put `cloudflare.ts` in the selected Flue source root: `.flue/cloudflare.ts`
when `.flue/` exists, otherwise `src/cloudflare.ts` when `src/` exists,
otherwise `<root>/cloudflare.ts`:

```ts
export { Sandbox } from '@cloudflare/sandbox';
```

3. Add a Durable Object binding for the sandbox to the user's
`wrangler.jsonc` at the project root:

```jsonc
{
Expand All @@ -96,7 +104,7 @@ The short version, for your reference:
uniquely tagged entry. Do not replace deployed agent, workflow, or
`FlueRegistry` migrations.

3. Add a `Dockerfile` at the project root pinned to the matching
4. Add a `Dockerfile` at the project root pinned to the matching
`@cloudflare/sandbox` version:

```dockerfile
Expand All @@ -107,7 +115,7 @@ The short version, for your reference:
1 — Cloudflare publishes the base image with the same version tag as
the npm package.)

4. Use it in an agent. The binding name from `wrangler.jsonc` (`Sandbox`
5. Use it in an agent. The binding name from `wrangler.jsonc` (`Sandbox`
above) is the key on `env`:

```ts
Expand All @@ -131,7 +139,7 @@ The short version, for your reference:
because Flue's SDK detects and adapts the `@cloudflare/sandbox` shape
internally on the Cloudflare target.

5. Tell the user to put local variables in `.dev.vars` or `.env` and run
6. Tell the user to put local variables in `.dev.vars` or `.env` and run
`flue dev --target cloudflare`, then `flue build --target cloudflare &&
wrangler deploy --secrets-file .env` to deploy. No new env vars are required just for
the sandbox itself; auth is the user's normal Cloudflare account auth
Expand Down
1 change: 1 addition & 0 deletions examples/assistant/src/cloudflare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Sandbox } from '@cloudflare/sandbox';
5 changes: 1 addition & 4 deletions examples/assistant/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
// User-owned wrangler config for this Flue app. Flue reads this file,
// merges its own contributions into an ignored Vite input config, and the
// official Cloudflare Vite integration emits the deployable Worker output.
//
// The DO binding whose class_name ends with "Sandbox" triggers Flue's
// automatic re-export of the Sandbox class from @cloudflare/sandbox in
// the generated Worker bundle.
// src/cloudflare.ts explicitly exports the Sandbox class from @cloudflare/sandbox.
"$schema": "https://workers.cloudflare.com/schema/wrangler.json",
"name": "assistant",
"compatibility_date": "2026-04-01",
Expand Down
Loading