Welcome, fellow AI systems engineer! Building truly intelligent AI agents for production goes far beyond simply calling a Large Language Model (LLM) API. It demands a robust system capable of managing state, integrating external tools, and executing complex logic reliably and securely. This is precisely the challenge that frameworks like Flue aim to solve.
In this chapter, we’ll dive deep into Flue, an “agent harness” framework designed to transform your AI agents from basic prompt wrappers into sophisticated, deployable entities. We’ll demystify its unique architecture, contrast it with traditional LLM SDKs, and guide you through building your very first Flue agent in TypeScript. By the end, you’ll have a foundational understanding and a working agent, ready for more advanced concepts and eventual deployment to platforms like Cloudflare Workers.
Why an “Agent Harness” Matters: Beyond LLM API Calls
Imagine you’re building a personal assistant agent. It needs to remember past conversations, look up information online, schedule appointments, and maybe even write code. A simple LLM API call can generate text, but it can’t inherently do any of these complex tasks, nor can it remember context across interactions.
This is where the “agent harness” concept, as embodied by Flue, becomes critical. As of its latest known state (June 2026), Flue provides the complete infrastructure—the “workbench”—around your core agent logic. It elevates an LLM from a smart text generator to a capable, stateful, and tool-using entity by offering:
- Stateful Sessions: Agents can maintain conversation history, internal variables, and context over extended interactions.
- Tool and Skill Integration: Agents gain the ability to use external functions, databases, APIs, or even sandboxed environments for tasks like web search or code execution.
- Sandboxed Execution: For advanced “coding agents,” Flue can provide secure, isolated environments (like sandboxed filesystems or shell access) where agents can safely write, test, and run code.
- Deployment & Exposure: Flue streamlines the process of exposing your agents via standard protocols (HTTP, WebSockets) for seamless integration into real-world applications.
Flue vs. LLM SDKs: A Clear Distinction
It’s easy to confuse an agent harness with a standard LLM SDK. Let’s clarify the difference:
LLM SDK Wrapper (e.g., OpenAI, Anthropic SDKs):
- Purpose: Primarily handles direct communication with a specific LLM API.
- Capabilities: Sends prompts, receives completions, manages API keys, handles rate limits.
- Analogy: A phone to call a smart friend.
- Limitation: It’s a communication layer. All the logic for state management, tool use, decision-making, and deployment must be built around the SDK by you.
Flue (Agent Harness Framework):
- Purpose: Provides the entire operating environment for an AI agent, orchestrating its lifecycle.
- Capabilities: Offers a runtime, manages sessions, orchestrates tool calls, handles input/output, and simplifies deployment. It uses LLM SDKs internally as one of its components.
- Analogy: A personal assistant (the agent) with a desk, filing cabinets (state), a phone (LLM SDK), and various specialized tools (web search, calculator) to accomplish complex tasks.
- Benefit: Reduces development time, enforces a consistent structure, and provides production-ready features out-of-the-box, allowing you to focus on the agent’s unique intelligence.
📌 Key Idea: Flue doesn’t replace your LLM SDK; it provides the robust infrastructure that uses your LLM SDK to build complete, intelligent, and deployable AI agents.
The Flue Agent Harness Architecture
Let’s visualize how Flue structures an agent system. This diagram illustrates the flow from a user request through the agent’s core components.
Understanding the Agent Workflow:
- User Request: A client (web app, mobile app, another service) initiates an interaction with your agent.
- Agent Endpoint: This is the public interface of your deployed agent, often an HTTP or WebSocket endpoint. In production, Flue’s
AgentRouteHandlertypically manages this. - Flue Agent Instance: This is your custom TypeScript agent code. It orchestrates the overall logic, deciding when to use the LLM, when to call tools, and how to manage state.
- Agent Session (State): This component is crucial for stateful interactions. It stores conversational history, user preferences, and any other data the agent needs to remember across multiple turns.
- Tools & Skills: When the agent needs to perform an action beyond text generation (e.g., retrieve data, perform calculations), it invokes one of its integrated tools or skills.
- LLM: The agent interacts with one or more LLMs for natural language understanding, generation, or complex reasoning.
- External Services: Tools often connect to external APIs, databases, or even sandboxed execution environments for specific tasks.
⚡ Real-world insight: This modular design allows for high flexibility. You can easily swap LLM providers, add new tools, or change deployment targets without extensive rework of your core agent logic. This is vital for adapting to evolving AI capabilities and business needs.
TypeScript-First Development
Flue is built with TypeScript at its core. This means you’ll define your agents, their inputs, outputs, and integrated tools using strong types. This approach provides:
- Type Safety: Catches errors early in development, reducing runtime bugs.
- Improved Readability: Clear interfaces make it easier to understand data flows.
- Enhanced Developer Experience: Modern IDEs provide excellent autocompletion and refactoring support.
For production-minded engineers, TypeScript is a significant advantage for building maintainable and robust AI systems.
Setting Up Your Flue Environment
Let’s prepare your development machine to build your first Flue agent.
Prerequisites
Please ensure you have the following installed:
- Node.js: We recommend Node.js
v20.xLTS or higher. You can download the installer from the official Node.js website. - npm or Yarn: These package managers are included with Node.js.
- TypeScript Knowledge: Familiarity with TypeScript syntax and concepts is crucial for working with Flue.
- Basic LLM Understanding: Knowing about prompts, completions, and how LLMs process information will be helpful.
Step 1: Initialize Your Project Directory
First, create a new folder for your project and initialize a Node.js project within it.
mkdir my-flue-agent
cd my-flue-agent
npm init -yThis command sets up a package.json file, which will manage your project’s dependencies and scripts.
Step 2: Install Flue Core and Development Dependencies
Next, install the core Flue library and essential development tools.
npm install @flue/core typescript @types/node ts-node express @types/expressLet’s understand what each package provides:
@flue/core: This is the foundational Flue library, containing theAgentbase class and core abstractions.typescript: The TypeScript compiler itself, which converts your.tsfiles into.js.@types/node: Type definitions for Node.js, enabling TypeScript to understand Node.js APIs.ts-node: A utility that allows you to execute TypeScript files directly in Node.js during development without a prior compilation step.express: A popular Node.js web framework. We’ll use it to create a simple local server for testing our agent.@types/express: TypeScript type definitions for the Express.js framework.
Step 3: Configure TypeScript
We need a tsconfig.json file to guide the TypeScript compiler. Create this file in your project’s root directory:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}Key compilerOptions Explained:
target: "ES2022": Specifies the ECMAScript version that your TypeScript code will be compiled down to. ES2022 offers modern JavaScript features.module: "Node16"andmoduleResolution: "Node16": These settings ensure that TypeScript uses Node.js’s modern module resolution strategy, aligning with how modules are handled in recent Node.js versions.esModuleInterop: true: Allows you to use the more commonimport x from "y"syntax even for modules that traditionally userequire("y").strict: true: Activates a suite of strict type-checking options, which is highly recommended for robust, production-grade code.outDir: "./dist": Designates the directory where compiled JavaScript files will be placed.
Step 4: Create a src Directory
Organize your source code by creating a src directory:
mkdir srcWith these steps, your development environment is now fully prepared to build Flue agents!
Building Your First Flue Agent: A Simple Greeter
Let’s create a basic Flue agent that simply greets a user by name. This will introduce you to the fundamental structure of any Flue agent.
Step 1: Define the Greeter Agent
Inside your newly created src directory, create a file named greeterAgent.ts.
// src/greeterAgent.ts
import { Agent, AgentContext } from '@flue/core';
/**
* Defines the expected input structure for our GreeterAgent.
* This agent needs a 'name' to personalize the greeting.
*/
interface GreeterAgentInput {
name: string;
}
/**
* Defines the expected output structure from our GreeterAgent.
* It will return an object containing the 'greeting' message.
*/
interface GreeterAgentOutput {
greeting: string;
}
/**
* The GreeterAgent class extends Flue's base Agent.
* It's generic, specifying its input and output types for strong type safety.
*/
export class GreeterAgent extends Agent<GreeterAgentInput, GreeterAgentOutput> {
/**
* The core method of any Flue agent. This is where your agent's
* specific business logic and decision-making resides.
*
* @param input The structured input data for this agent invocation.
* @param context Provides access to Flue's runtime environment (e.g., session, tools).
* @returns A Promise that resolves to the structured output of the agent.
*/
async handle(
input: GreeterAgentInput,
context: AgentContext
): Promise<GreeterAgentOutput> {
// We extract the 'name' property from the agent's input.
const { name } = input;
// Construct a personalized greeting message using a template literal.
const greeting = `Hello, ${name}! Welcome to Flue.`;
// Log the generated greeting to the console for debugging and observation.
console.log(`GreeterAgent generated: ${greeting}`);
// Return the structured output, conforming to the GreeterAgentOutput interface.
return { greeting };
}
}Code Explanation:
import { Agent, AgentContext } from '@flue/core';: We importAgent, the base class for all Flue agents, andAgentContext, which provides runtime utilities.interface GreeterAgentInput { name: string; }: We define the expected input type. This enhances type safety and makes the agent’s contract clear.interface GreeterAgentOutput { greeting: string; }: We define the expected output type. The agent’shandlemethod must return an object matching this structure.export class GreeterAgent extends Agent<GreeterAgentInput, GreeterAgentOutput> { ... }: OurGreeterAgentclass inherits from Flue’sAgentclass. The generic types<GreeterAgentInput, GreeterAgentOutput>tell Flue (and TypeScript) what kind of input this agent expects and what output it will produce.async handle(input: GreeterAgentInput, context: AgentContext): Promise<GreeterAgentOutput> { ... }: This is the most important method for any Flue agent. It’s where your agent’s core logic executes.- It’s
asyncbecause real-world agent operations (like calling an LLM or a tool) are typically asynchronous. input: Contains the data passed to the agent, strictly typed byGreeterAgentInput.context: An object providing access to Flue’s runtime context, including session, tools, and potentially other environment variables. For this simple agent, we don’t use it, but it’s always available.Promise<GreeterAgentOutput>: Specifies that the method will return a promise that resolves to an object matching ourGreeterAgentOutputinterface.
- It’s
const { name } = input;: We safely extract thenamefrom the typedinputobject.const greeting = \Hello, ${name}! Welcome to Flue.`;`: A simple string interpolation to create the greeting.console.log(...): A useful line for observing the agent’s internal activity during development.return { greeting };: The agent returns its structured output.
Testing Your Flue Agent Locally with Express
To interact with our GreeterAgent, we need a way to send it input and receive its output. For local development, we’ll use a simple Express.js server as a test harness.
Step 1: Create a Local Server Entry Point
Create a new file in your src directory named server.ts. This file will set up our Express server and expose our GreeterAgent.
// src/server.ts
import express from 'express';
import { GreeterAgent } from './greeterAgent';
import { AgentContext } from '@flue/core'; // We'll use AgentContext, even if empty for now
// Initialize an Express application
const app = express();
// Enable JSON body parsing for incoming requests
app.use(express.json());
// Create an instance of our GreeterAgent
const greeterAgent = new GreeterAgent();
// Define a POST endpoint to interact with our agent
app.post('/greet', async (req, res) => {
try {
// For local testing, we directly invoke the agent's handle method.
// In a production Flue deployment (e.g., Cloudflare Workers),
// Flue's AgentRouteHandler would abstract this request parsing.
const agentInput = req.body; // Express automatically parses JSON body into req.body
// Create an empty context for this simple agent,
// but in real agents, this would contain session info, tools, etc.
const context: AgentContext = {};
const result = await greeterAgent.handle(agentInput, context);
res.json(result); // Send the agent's structured output as a JSON response
} catch (error) {
console.error('Error handling agent request:', error);
// Provide a generic error response for security and simplicity
res.status(500).json({ error: 'Internal server error processing agent request' });
}
});
// Start the Express server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Flue Greeter Agent local server listening on port ${PORT}`);
console.log(`To test, send a POST request to http://localhost:${PORT}/greet`);
console.log(`Example JSON body: {"name": "Alice"}`);
});Step 2: Add npm Scripts for Convenience
To easily run and build your project, add these scripts to the scripts section of your package.json file. Replace the placeholder versions with the actual versions installed.
// package.json (update 'scripts' and 'dependencies' sections)
{
"name": "my-flue-agent",
"version": "1.0.0",
"description": "My first Flue agent project.",
"main": "dist/server.js",
"scripts": {
"start": "ts-node src/server.ts",
"build": "tsc",
"serve": "node dist/server.js"
},
"keywords": ["flue", "ai-agent", "typescript"],
"author": "AI Expert",
"license": "ISC",
"dependencies": {
"@flue/core": "^0.x.x",
"express": "^4.19.2",
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.14.9",
"ts-node": "^10.9.2"
}
}Note: The versions above are examples. npm install will fetch the latest compatible versions, which you can then update in your package.json if you wish to pin them.
Step 3: Run Your Local Agent Server
Now, let’s start your Express server using the start script we just defined.
npm startYou should see output indicating the server is running:
Flue Greeter Agent local server listening on port 3000
To test, send a POST request to http://localhost:3000/greet
Example JSON body: {"name": "Alice"}Step 4: Test Your Agent
You can test your running agent using curl from your terminal, or a tool like Postman or Insomnia.
curl -X POST -H "Content-Type: application/json" \
-d '{"name": "Alice"}' \
http://localhost:3000/greetIf everything is set up correctly, you should receive a JSON response:
{"greeting":"Hello, Alice! Welcome to Flue."}Congratulations! You’ve successfully built and tested your first Flue agent locally.
Understanding AgentRouteHandler for Production Deployment
While our Express server is great for local testing, Flue provides a more integrated solution for exposing agents in production: the AgentRouteHandler from @flue/server. This component is specifically designed to bridge your Flue agent with serverless environments like Cloudflare Workers.
AgentRouteHandler’s primary role is to:
- Receive incoming HTTP or WebSocket requests.
- Parse these requests into the standardized
AgentInputformat expected by your Flue agent. - Manage agent sessions, ensuring state is correctly loaded and saved.
- Invoke your agent’s
handlemethod with the properAgentContext. - Format the agent’s
AgentOutputback into a standard HTTP/WebSocket response.
⚡ Real-world insight: For serverless platforms like Cloudflare Workers, AgentRouteHandler acts as the direct fetch event handler, abstracting away much of the boilerplate for agent invocation and response handling. This is a key differentiator for Flue as a production-minded framework.
Here’s a conceptual example of how AgentRouteHandler would be used in a Cloudflare Worker, as referenced in the official Flue documentation:
// worker.ts (Conceptual Cloudflare Worker entry point)
import { AgentRouteHandler } from '@flue/server';
import { GreeterAgent } from './greeterAgent'; // Your Flue agent
// Instantiate your agent
const greeterAgent = new GreeterAgent();
// Create the AgentRouteHandler, passing your agent instance
const agentHandler = new AgentRouteHandler(greeterAgent);
// Export the Worker's fetch handler, delegating to AgentRouteHandler
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// The AgentRouteHandler processes the incoming request and invokes the agent
return agentHandler.fetch(request, env, ctx);
},
};We’ll explore full deployment to Cloudflare Workers and other production considerations in a dedicated future chapter. For now, understand that AgentRouteHandler is the intended gateway for your agents in scalable, production environments.
Mini-Challenge: Personalize the Greeting with Time
Now that you’ve built and tested a basic agent, let’s make it a little more dynamic.
Challenge: Modify the GreeterAgent to include the current time in the greeting message. For example, “Good morning, Alice! Welcome to Flue. It’s 10:30 AM.”
Hint:
- You can obtain the current local time string using
new Date().toLocaleTimeString(). - Remember to update the
GreeterAgentOutputinterface if you decide to return the time as a separate field, or simply embed it in the existinggreetingstring.
What to observe/learn: This challenge reinforces your understanding of modifying agent logic, handling basic data manipulation within the handle method, and ensuring that your agent’s output consistently matches its defined Output interface.
Common Pitfalls & Troubleshooting
As you build and experiment with Flue, you might encounter some common issues. Here’s how to approach them:
TypeError: Cannot read properties of undefined (reading 'name'): This error typically means thenameproperty was not found in theinputobject passed to your agent. Double-check yourcurlcommand or Postman request to ensure you are sending a valid JSON body, like{"name": "Alice"}. Also, ensure your Express server is usingapp.use(express.json());to correctly parse incoming JSON.- TypeScript Compilation Errors: If
npm start(ornpm run build) fails with errors, carefully read the messages. They usually provide the file name and line number. Common causes include:- Missing Imports: Did you forget to import
AgentorAgentContextfrom@flue/core? - Type Mismatches: Is the object returned by your
handlemethod compatible with theGreeterAgentOutputinterface you defined? TypeScript will enforce this. tsconfig.jsonIssues: Ensure yourtsconfig.jsonis correctly configured, especially theincludeandoutDirpaths, and thatstrict: truehelps catch potential issues early.
- Missing Imports: Did you forget to import
- Server Not Starting: If your Express server doesn’t log the “listening on port…” message, it might be due to another process already using port
3000. You can change thePORTvariable insrc/server.tsor identify and terminate the conflicting process. - Confusion with
AgentRouteHandler: Remember that for our simple local Express example, we directly callgreeterAgent.handle(). Do not confuse this local testing pattern with theAgentRouteHandler’s role in a full production deployment, where it directly handles the serverlessfetchevent. The local setup is a simplified mock.
Summary
You’ve successfully completed your first journey into the Flue Framework! Here’s a quick recap of the key takeaways:
- Agent Harness Architecture: Flue provides a comprehensive framework for building sophisticated AI agents, going beyond simple LLM API calls to manage state, integrate tools, and provide a secure execution environment.
- Distinction from LLM SDKs: We clarified that Flue is an orchestrator and runtime for agents, while LLM SDKs are primarily communication layers for interacting with LLMs. Flue uses LLM SDKs.
- TypeScript-First Development: Flue leverages TypeScript for robust, type-safe, and maintainable agent code, a critical advantage for production systems.
- Core Agent Structure: You learned to define a Flue agent by extending the
Agentclass and implementing itshandlemethod, along with defining clear input and output interfaces. - Local Agent Testing: You set up a local Express server to test your agent, understanding how to invoke its logic and receive structured responses.
AgentRouteHandlerfor Production: You gained an initial understanding ofAgentRouteHandler’s role as Flue’s primary deployment mechanism for serverless environments like Cloudflare Workers.
In the upcoming chapters, we’ll delve deeper into crucial aspects like stateful sessions, tool integration, and advanced deployment strategies, empowering you to build truly intelligent and interactive AI experiences.
References
- Flue — The Agent Harness Framework
- withastro/flue: The sandbox agent framework. - GitHub
- Node.js Official Website
- TypeScript Official Website
- Express.js Official Website
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.