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.

flowchart LR User -->|Request| AgentEndpoint[Agent Endpoint] AgentEndpoint -->|Routes Input| FlueAgent[Flue Agent] FlueAgent -->|Accesses| AgentSession[Agent Session State] FlueAgent -->|Invokes| ToolsSkills[Tools and Skills] FlueAgent -->|Interacts with| LLM[Large Language Model] ToolsSkills --> ExternalServices[External Services] LLM --> FlueAgent AgentSession --> FlueAgent FlueAgent -->|Returns Output| AgentEndpoint AgentEndpoint -->|Response| User

Understanding the Agent Workflow:

  1. User Request: A client (web app, mobile app, another service) initiates an interaction with your agent.
  2. Agent Endpoint: This is the public interface of your deployed agent, often an HTTP or WebSocket endpoint. In production, Flue’s AgentRouteHandler typically manages this.
  3. 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.
  4. 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.
  5. 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.
  6. LLM: The agent interacts with one or more LLMs for natural language understanding, generation, or complex reasoning.
  7. 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.x LTS 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 -y

This 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/express

Let’s understand what each package provides:

  • @flue/core: This is the foundational Flue library, containing the Agent base class and core abstractions.
  • typescript: The TypeScript compiler itself, which converts your .ts files 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" and moduleResolution: "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 common import x from "y" syntax even for modules that traditionally use require("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 src

With 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 import Agent, the base class for all Flue agents, and AgentContext, 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’s handle method must return an object matching this structure.
  • export class GreeterAgent extends Agent<GreeterAgentInput, GreeterAgentOutput> { ... }: Our GreeterAgent class inherits from Flue’s Agent class. 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 async because real-world agent operations (like calling an LLM or a tool) are typically asynchronous.
    • input: Contains the data passed to the agent, strictly typed by GreeterAgentInput.
    • 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 our GreeterAgentOutput interface.
  • const { name } = input;: We safely extract the name from the typed input object.
  • 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 start

You 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/greet

If 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 AgentInput format expected by your Flue agent.
  • Manage agent sessions, ensuring state is correctly loaded and saved.
  • Invoke your agent’s handle method with the proper AgentContext.
  • Format the agent’s AgentOutput back 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 GreeterAgentOutput interface if you decide to return the time as a separate field, or simply embed it in the existing greeting string.

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 the name property was not found in the input object passed to your agent. Double-check your curl command or Postman request to ensure you are sending a valid JSON body, like {"name": "Alice"}. Also, ensure your Express server is using app.use(express.json()); to correctly parse incoming JSON.
  • TypeScript Compilation Errors: If npm start (or npm 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 Agent or AgentContext from @flue/core?
    • Type Mismatches: Is the object returned by your handle method compatible with the GreeterAgentOutput interface you defined? TypeScript will enforce this.
    • tsconfig.json Issues: Ensure your tsconfig.json is correctly configured, especially the include and outDir paths, and that strict: true helps catch potential issues early.
  • 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 the PORT variable in src/server.ts or identify and terminate the conflicting process.
  • Confusion with AgentRouteHandler: Remember that for our simple local Express example, we directly call greeterAgent.handle(). Do not confuse this local testing pattern with the AgentRouteHandler’s role in a full production deployment, where it directly handles the serverless fetch event. 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 Agent class and implementing its handle method, 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.
  • AgentRouteHandler for Production: You gained an initial understanding of AgentRouteHandler’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

This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.