Skip to content
Blog

Building a Flue Agent: A Step-by-Step Tutorial

Learn how to build a conversational AI agent using Flue, a framework for creating durable, tool-using agents with weather and IT policy capabilities.

Published on September 16, 2026

AI Assistant

Building a Flue Agent: A Step-by-Step Tutorial

This tutorial walks through creating a conversational AI agent using Flue, a framework for building durable, tool-using agents. We’ll build a weather-aware assistant with IT policy knowledge.

Project Structure

flue-sample/
├── src/
│   ├── agents/          # Agent definitions
│   ├── skills/          # Reusable knowledge bases
│   ├── tools/           # External integrations
│   ├── app.ts           # HTTP server
│   └── db.ts            # Persistence layer
├── data/                # SQLite database
├── flue.config.ts       # Flue configuration
├── package.json
└── vite.config.ts

1. Setup

Initialize the project

npm install

Configure environment

Create .env with your model provider API key:

# .env
GEMINI_API_KEY="your-key-here"

Any Pi-supported provider works (OpenAI, Anthropic, Google, etc.).

2. Create a Weather Tool

Tools let agents fetch data from external services. Create src/tools/weather.ts:

import { defineTool } from '@flue/runtime/tool';
import * as v from 'valibot';

export const weather = defineTool({
  name: 'get_weather',
  description: 'Get current weather and forecast for a location using wttr.in.',
  input: v.object({
    location: v.string(),
  }),
  async run({ data }) {
    const res = await fetch(
      `http://wttr.in/${encodeURIComponent(data.location)}?format=j1`,
    );
    if (!res.ok) {
      return `Failed to fetch weather for "${data.location}". Status: ${res.status}`;
    }
    const json: any = await res.json();
    const current = json.current_condition?.[0];
    if (!current) return `No weather data available for "${data.location}".`;

    const currentWeather = [
      `Location: ${json.nearest_area?.[0]?.areaName?.[0]?.value ?? data.location}`,
      `Temperature: ${current.temp_C}°C (${current.temp_F}°F)`,
      `Feels Like: ${current.FeelsLikeC}°C (${current.FeelsLikeF}°F)`,
      `Condition: ${current.weatherDesc?.[0]?.value ?? 'Unknown'}`,
      `Humidity: ${current.humidity}%`,
      `Wind: ${current.windspeedKmph} km/h ${current.winddir16Point}`,
    ].join('\n');

    const forecastLines: string[] = [];
    for (const day of json.weather ?? []) {
      forecastLines.push(
        `${day.date}: ${day.mintempC}°C – ${day.maxtempC}°C, ${day.hourly?.[4]?.weatherDesc?.[0]?.value ?? 'Unknown'}`,
      );
    }

    let output = `**Current Weather**\n${currentWeather}`;
    if (forecastLines.length) {
      output += `\n\n**Forecast**\n${forecastLines.join('\n')}`;
    }
    return output;
  },
});

Key points:

  • defineTool registers a tool the agent can call
  • input uses Valibot schemas for validation
  • run executes the tool and returns a string result

3. Create a Skill

Skills provide domain knowledge. Create src/skills/it-policy.ts:

import { defineSkill } from '@flue/runtime';

export const itPolicy = defineSkill({
  name: 'it-policy',
  description: 'IT and security policy knowledge base.',
  instructions: `
## IT Policy

### Acceptable Use
- Company devices are for work purposes
- Do not install unauthorized software
- Report lost devices within 24 hours

### Password & Authentication
- Minimum 12 characters with mixed case, numbers, symbols
- Enable MFA on all accounts
- Use 1Password for all credentials

### Data Security
| Classification | Examples | Handling |
|----------------|----------|----------|
| Public | Marketing docs | No restrictions |
| Internal | Memos, org charts | Company only |
| Confidential | Financials, PII | Encrypted, need-to-know |
| Restricted | Source code, keys | Never plain text |
  `.trim(),
});

Key points:

  • Skills are reusable knowledge modules
  • The agent automatically uses relevant skills based on user queries
  • Instructions are injected into the agent’s context when the skill is invoked

4. Define the Agent

The agent ties everything together. Create src/agents/assistant.ts:

'use agent';
import { useModel, useSandbox, useMcpConnection, useSkill, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import { weather } from '../tools/weather.ts';
import { itPolicy } from '../skills/it-policy.ts';

export function Assistant() {
  useModel('google/gemini-2.5-flash');
  useSandbox(local());
  useMcpConnection({
    name: 'local-mcp',
    url: 'http://localhost:9100/mcp',
    optional: true,
  });
  useSkill(itPolicy);
  useTool(weather);
  return 'You are a helpful assistant. Keep replies short.';
}

Key points:

  • 'use agent' directive marks this as an agent module
  • useModel selects the LLM provider
  • useSandbox(local()) enables code execution
  • useMcpConnection connects to external MCP servers (optional)
  • useSkill and useTool register capabilities
  • The return value is the system prompt

5. Set Up Persistence

Create src/db.ts for durable conversations:

import { sqlite } from '@flue/runtime/node';

// Conversations survive restarts. Swap to Postgres/libSQL when needed.
export default sqlite('./data/flue.db');

6. Create the HTTP Server

Create src/app.ts to expose the agent via HTTP:

import { createAgentRouter } from '@flue/runtime/routing';
import { Hono } from 'hono';
import { Assistant } from './agents/assistant.ts';

const app = new Hono();

app.get('/health', (c) => c.json({ status: 'ok' }));
app.route('/agents/assistant', createAgentRouter(Assistant));

export default app;

7. Run the Agent

CLI mode (no server)

npx flue run src/agents/assistant.ts --message "What's the weather in London?"

HTTP server mode

npm run dev

Then send requests to http://localhost:3000/agents/assistant.

8. Conversation Persistence

Conversations are durable by default. Continue a previous conversation:

npx flue run src/agents/assistant.ts --id conv_01M2JJXVH96PJB4BW14P7NXZTV --message "Tell me a joke"

Configuration Files

flue.config.ts

import { defineConfig } from '@flue/runtime/config';

export default defineConfig({
  target: 'node',
});

vite.config.ts

import { flue } from '@flue/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [flue()],
});

package.json

{
  "dependencies": {
    "@flue/runtime": "^2.0.5",
    "hono": "^4.13.8"
  },
  "devDependencies": {
    "@flue/cli": "^2.0.5",
    "@flue/vite": "^2.0.6"
  }
}

Next Steps

  • Add more tools (databases, APIs, file systems)
  • Create additional skills for different domains
  • Deploy to production with npm run build
  • Connect to external MCP servers for expanded capabilities
  • Swap SQLite for Postgres in production

Useful Commands

CommandDescription
npm run check:typesType-check the project
npm run buildBuild for production
npx flue docs search <query>Search Flue documentation
npx flue addList available blueprints