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:
defineToolregisters a tool the agent can callinputuses Valibot schemas for validationrunexecutes 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 moduleuseModelselects the LLM provideruseSandbox(local())enables code executionuseMcpConnectionconnects to external MCP servers (optional)useSkillanduseToolregister 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
| Command | Description |
|---|---|
npm run check:types | Type-check the project |
npm run build | Build for production |
npx flue docs search <query> | Search Flue documentation |
npx flue add | List available blueprints |