Flue structures every agent run around a strict hierarchy of objects. Understanding what each object represents, who owns it, and how long it lives is the fastest way to reason about state, persistence, and isolation in your agents.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/withastro/flue/llms.txt
Use this file to discover all available pages before exploring further.
The hierarchy at a glance
Agent
An agent is a TypeScript file in.flue/agents/. Its filename determines its name: hello.ts → agent hello, reachable at POST /agents/hello/<id>.
Every agent file exports two things:
triggers— an object that tells Flue how to invoke the agent.{ webhook: true }exposes the agent as an HTTP endpoint. An empty{}means the agent can only be invoked from the CLI viaflue run.- A default export handler — an
async functionthat receives aFlueContextand returns a result.
.flue/agents/ when a .flue/ directory exists at the project root. If there is no .flue/ directory, Flue falls back to agents/ at the root. The two layouts never mix.
Agents have names (derived from the filename). Agent instances have ids (from the URL). Harnesses and sessions have names. Runs and operations have server-minted ids.
AgentInstance
An AgentInstance is identified by the<id> segment in the request URL:
<id> is caller-defined. It is exposed to your handler as ctx.id. Think of the instance as a durable workspace — it is the scope for:
- Sandbox state: files written to the filesystem during a run persist to the same instance’s sandbox (for remote sandboxes; virtual sandboxes are in-memory per run unless you share the filesystem object in a closure).
- Session history: conversations (message history) are stored and retrieved by instance id + session name.
<id>: you want to continue an existing conversation or pick up where a previous run left off. The model sees the full prior message history.
When to use a new <id>: you want a completely fresh conversation — a new customer, a new repository, a new isolated thread.
<id> for each distinct conversation space — a customer id, a repository name, a ticket number, or any other boundary that makes sense for your domain.
Run
A Run is one HTTP invocation of an agent. It is created by the server when a request arrives and destroyed when the handler returns. Every run gets a server-mintedrunId exposed as ctx.runId.
Runs are short-lived. They are not directly durable — persistence happens at the session level (via the session store) and the sandbox level (via the remote sandbox provider).
Harness
A Harness is the object returned byinit(). It is the central configuration handle for a run: it holds the default model, sandbox, tool list, and session registry.
init() accepts an AgentInit options object. Key fields:
| Field | Description |
|---|---|
model | Default model for all sessions in this harness. Format: 'provider/model-id'. |
sandbox | Sandbox type: omit for virtual (default), local() for host access, or a remote connector. |
tools | Harness-wide tools available to every prompt(), skill(), and task() call. |
role | Harness-wide default role. Overridden by session-level or per-call roles. |
name | Harness name. Defaults to "default". |
cwd | Working directory for context discovery and shell calls. |
init({ name: 'project' }) a second time to get a second, independently-configured harness — useful when you want two isolated contexts (e.g., a setup harness and a project harness) that share the same sandbox:
Session
A Session is the message history and conversation metadata for one conversation thread inside a harness. The default session is named"default". Open a named session with harness.session(threadName):
SessionStore to init({ persist }) for durable persistence.
Session operations:
session.prompt(text, options?)— send a user message and wait for the model’s response.session.skill(name, options?)— invoke a named skill (a Markdown instruction file) within the session.session.task(text, options?)— run a one-shot child agent in a detached session with its own message history.session.shell(command, options?)— run a shell command in the sandbox, recorded in the conversation transcript.session.compact()— trigger context compaction immediately (summarizes older messages to free context window space).
harness.sessions.get(), .create(), and .delete() for explicit session lifecycle management.
Operation
An Operation is onesession.prompt(), session.skill(), session.task(), or session.shell() call. Each operation gets a server-minted operationId. Operations emit events on the run’s SSE stream (e.g. operation_start, operation).
Operations return a CallHandle<T> — a PromiseLike that you can await directly, or cancel by calling .abort().
Typed results
Pass a Valibot schema asresult: to get schema-validated, fully-typed data back from any LLM operation:
Tasks
session.task() runs a focused, one-shot child agent in a detached session. Tasks share the same sandbox and filesystem as the parent, but get their own message history and discover AGENTS.md and skills from their own working directory:
Turn
A Turn is one LLM round-trip inside an operation. An operation may produce multiple turns if the model calls tools — each tool call + response is a turn. Turns emitturn events on the SSE stream with duration, model id, token usage, and stop reason.
Sandboxes
Every harness has a sandbox — the environment where shell commands run and files are read and written. Flue supports three sandbox modes:Virtual sandbox (default)
Virtual sandbox (default)
The default sandbox is a fast, in-process virtual environment powered by just-bash. It has no real filesystem or network access unless you opt in, but the agent can use grep, glob, read, write, and other built-in tools against an in-memory filesystem.Use the virtual sandbox for high-traffic agents that don’t need a real shell. It starts instantly, costs nothing to spin up, and scales to any request volume.To share an in-memory filesystem across sessions (so files written in one prompt are visible in the next), create the filesystem object outside
init() and share it in a closure:Local sandbox (Node.js only)
Local sandbox (Node.js only)
local() gives the agent direct access to the host filesystem and shell. The agent’s bash tool can run gh, git, npm, and any other binary on $PATH. Use this in CI runners where the runner itself is your isolation boundary.PATH, HOME, locale vars) is inherited from process.env by default. Explicitly pass any additional env vars you need.Remote sandbox (Daytona, E2B, Cloudflare Containers)
Remote sandbox (Daytona, E2B, Cloudflare Containers)
For full coding agents, you want a real Linux environment with persistent storage. Remote connectors adapt third-party sandbox providers into Flue’s The CLI fetches the connector’s installation instructions and pipes them to your coding agent, which writes a small TypeScript adapter into Connectors are available for Daytona, E2B, and Cloudflare Containers. You can also build a custom connector from a provider’s docs URL:
SandboxFactory interface.Install a connector with flue add:.flue/connectors/<name>.ts. You then import the adapter directly:Roles and skills
Roles are Markdown files in.flue/roles/ that define a persona for the agent: instructions, a preferred model, and an optional reasoning effort level. Apply a role at the harness, session, or call level. Per-call roles override session roles, which override harness roles:
.flue/skills/ that describe a procedure the agent should follow. Invoke a skill by name or path:
Session persistence
- Cloudflare (Durable Objects)
- Node.js (in-memory)
When you deploy with
--target cloudflare, Flue automatically backs every session with a Durable Object. Session data is stored in SQLite inside the DO and survives indefinitely across requests, process restarts, and deployments.No configuration is required. The session store is wired up automatically by the generated Cloudflare entry point.Multi-target deployment
Flue agents are runtime-agnostic. The same agent file builds to different targets without any code changes:@flue/runtime/node for local(), @flue/runtime/cloudflare for getVirtualSandbox()) and Cloudflare-specific bindings like env.AI for Workers AI. Everything else — init(), harness.session(), session.prompt(), connectMcpServer() — is identical across targets.