I/D/E · flue-framework

Build Targets And Deployment Shape

Summary

How Flue becomes a framework by generating Node and Cloudflare runtime shapes from the same agent source.

Flue is a framework because deployment is part of the contract. The runtime is not only session.prompt(...). It is also the generated server or worker that loads agents, configures stores, creates sandboxes, registers providers, mounts flue(), and exposes a stable inspection surface.

The same user agent source can become a Node server or a Cloudflare Worker. The agent handler stays recognizable; the generated runtime shape changes because the host changes the available bindings and stores.

The source pin for this chapter is withastro/flue@dbaa9effa305561c627c6836559f8a0cbce67875.

Generated Entry Boot Sequence

Build targets matter because the generated entry wires runtime configuration, stores, bindings, and routes before requests arrive.

Domain Word

In Flue, a build target is a generated runtime shape such as Node or Cloudflare. It is not an example folder. It is the part of the framework that adapts the same agent source to a host.

The invariant is: deployment target changes adapters and bindings, not the core harness language.

The twelve-factor pressure is build, release, run and dev/prod parity. Build-time discovery, release artifacts, and runtime invocation have different jobs, but they should preserve the same concepts.

Build Context

packages/cli/src/lib/build.ts is the entry point. It discovers the project root, agents, roles, optional app entry, output directory, and target plugin. It emits a manifest, generates a target-specific entry point, and either bundles with esbuild or writes a pass-through entry for a downstream bundler.

BUILD FLOW
project root
 .flue/agents/*.ts
 optional roles and app.ts
 flue.config.*
      
      
build.ts
 resolve config
 discover agents
 create manifest
 call target plugin
      
       Node plugin -> bundled server.mjs
       Cloudflare plugin -> _entry.ts + wrangler.jsonc

The manifest is the bridge between source discovery and runtime configuration. Generated code uses it to configure the runtime before requests arrive.

Config Lifecycle

packages/cli/src/lib/config.ts is explicit about config scope. flue.config.* handles project-level build settings such as target, root, and output. The comments also say provider/model configuration belongs in app.ts, where runtime environment is available.

That separation matters:

PhaseBelongs here
Build configTarget, root, output, config-file discovery.
Runtime appProvider registration, provider settings, middleware, app composition.
Agent handlerWork declaration and calls into the harness.

Secrets and platform bindings are runtime concerns. A build config should not pretend it has the same lifecycle as a deployed Worker request or Node process.

Node Generated Entry

packages/cli/src/lib/build-plugin-node.ts generates a Node/Hono runtime. The generated entry imports the discovered agent handlers, runtime helpers, stores, registry, local session env support, and optional user app.

The Node entry does a few load-bearing things before serving:

StepWhy
Build handler map and webhook agent listLets public routes validate and dispatch agents.
Create in-memory run registry and subscribersSupports bare run lookup and live stream tailing in-process.
Configure local session envGives Node a host-backed execution/filesystem boundary.
Call configureFlueRuntime(...)Seeds flue() before routes handle requests.
Mount default app or user appLets simple projects run without custom Hono code.

The generated entry’s job is intentionally narrow: import handlers, build runtime config, instantiate sandboxes/stores, and start the listener.

Cloudflare Generated Entry

packages/cli/src/lib/build-plugin-cloudflare.ts generates a Worker entry. It differs from Node because Cloudflare has request-scoped environment bindings, Durable Objects, Workers AI bindings, and Wrangler as the downstream bundler.

The Cloudflare plugin:

  • skips Flue’s esbuild pass and lets Wrangler bundle
  • writes _entry.ts
  • merges or creates wrangler.jsonc
  • registers the Cloudflare provider if needed
  • detects user sandbox bindings
  • wires request-specific session/run stores, using Durable Object storage when available
  • configures a request-specific run registry
  • exports per-agent Durable Object classes

That is a lot of code, but it is all target adaptation. The public concepts stay the same: agents, harness, sessions, runs, registry, stream routes.

Target Fan-Out

ConcernNode targetCloudflare target
Server shapeHono server served by @hono/node-serverWorker fetch handler
BundlingFlue/esbuild bundleWrangler bundles generated entry
Session storeIn-memory/local process defaultsRequest-scoped store; DO SQLite when available
Run registryIn-memory registryRequest-scoped registry; Durable Object aware
SandboxLocal session env or user app compositionCloudflare sandbox/bindings
Provider bindingRuntime app or env lookupWorkers AI binding registration path
Config outputbundled server artifact_entry.ts and wrangler.jsonc

The target changes the plumbing. It should not change how a reader understands a session or run.

app.ts Composition

Both plugins account for optional user app composition. If no app.ts exists, generated code builds a default app that mounts flue() at root. If a user app exists, generated code imports it and passes the listener through.

This is the right compromise. Simple projects get a working service. Advanced projects can mount flue() and admin() behind their own middleware, auth, paths, and observability.

Why This Is Not An Example Folder

Example folders teach usage. Build targets carry product behavior.

If a target forgets to configure the run registry, GET /runs/:runId breaks. If Cloudflare generation forgets the Durable Object backed storage path, session persistence semantics change when requests route through agent objects. If Node generation starts before configureFlueRuntime(...), public routes can exist without a configured runtime.

Those are framework bugs, not example bugs.

What Breaks If This Boundary Drifts

DriftFailure
Build config owns runtime secretsProviders work locally and fail at deploy time.
Generated entry starts before runtime configurationflue() routes exist but cannot dispatch or inspect runs.
Node assumptions leak into CloudflareIn-process registries replace Durable Object backed lookup.
Cloudflare assumptions leak into NodeSimple local runs require platform bindings they should not need.
Build targets are treated as samplesDeployment regressions stop being tested as framework behavior.

What To Copy

The copyable pattern is target-specific generation around target-independent vocabulary. Keep “agent”, “harness”, “session”, “run”, and “registry” stable, then adapt stores, bindings, sandbox creation, and server boot for the host.

That lets documentation, SDKs, and tests talk about one framework even when deployment artifacts look different.

Verify In Source

  • build.ts discovers agents, writes manifest.json, asks a target plugin for entry code, and handles bundle strategy.
  • config.ts limits flue.config.* to build-level target/root/output concerns.
  • build-plugin-node.ts generates a Hono server and calls configureFlueRuntime(...).
  • build-plugin-node.ts creates in-memory run registry/subscriber support for Node.
  • build-plugin-cloudflare.ts writes _entry.ts, creates/merges wrangler.jsonc, and defers bundling to Wrangler.
  • build-plugin-cloudflare.ts registers Cloudflare provider support and wires Durable Object backed runtime pieces.
  • Both plugins support optional user app composition.

References

Flue-framework Ch 7/8
  1. 1 Runtime Map 24m
  2. 2 Session Tree, Leaf, And Replay Safety 26m
  3. 3 The Pi-ai Seam 22m
  4. 4 Compaction As Failure Recovery 28m
  5. 5 Tool Contracts And Sandbox Reality 25m
  6. 6 Runs, Registries, Logs, And APIs 27m
  7. 7 Build Targets And Deployment Shape 26m
  8. 8 Extending Flue Safely 24m