Organizing Flue Agents: .flue Directory Layout vs Root-Level Layout

Flue enforces an exclusive choice between two project structures: if a .flue/ directory exists, all agents and roles are loaded from it exclusively; otherwise, Flue reads from root-level agents/ and roles/ folders.

When working with the withastro/flue SDK, you must decide how to structure your AI agents and role definitions. The framework supports two mutually exclusive layouts, automatically detecting which convention your project follows through a deterministic resolution algorithm implemented in the build pipeline.

Understanding the Two Layout Options

Flue discovers agents and roles from exactly one location, never both. The decision hinges on the presence of a .flue/ directory at your project root.

Root-Level Layout

In this convention, agent definitions live directly under the project root alongside your other source files.


my-project/
├─ agents/
│   └─ hello.ts
├─ roles/
│   └─ analyst.md
└─ flue.config.ts

Use this layout when you want Flue code co-located with your application source, or when your project follows a traditional structure without a dedicated configuration folder.

.flue Directory Layout

This alternative nests all Flue-specific files inside a hidden .flue/ directory, similar to the .src/ or .app/ patterns used by modern frameworks.


my-project/
├─ .flue/
│   ├─ agents/
│   │   └─ translate.ts
│   ├─ roles/
│   │   └─ analyst.md
│   └─ app.ts        # optional custom app entry

└─ flue.config.ts

Critical rule: The presence of .flue/ wins unconditionally. Even if you maintain both agents/ at root and .flue/agents/, Flue ignores the root-level folders entirely when .flue/ exists.

How Flue Resolves the Source Root

The core logic resides in packages/sdk/src/build.ts, specifically within the resolveSourceRoot() helper function:

// packages/sdk/src/build.ts
export function resolveSourceRoot(root: string): string {
  const dotFlue = path.join(root, '.flue');
  if (fs.existsSync(dotFlue)) return dotFlue;   // <-- .flue layout takes precedence
  return root;                                 // <-- otherwise use the root
}

Source: [build.ts lines 94-98](https://github.com/withastro/flue/blob/main/packages/sdk/src/build.ts#L94-L98)

This function returns the effective source root, which then propagates through the entire build pipeline. During initialization, Flue calls this resolver once and uses the returned path for all subsequent discovery operations:

const sourceRoot = resolveSourceRoot(root);
const roles   = discoverRoles(sourceRoot);
const agents  = discoverAgents(sourceRoot);

Source: [build.ts lines 106-118](https://github.com/withastro/flue/blob/main/packages/sdk/src/build.ts#L106-L118)

Because resolveSourceRoot() executes at the entry point of every command (build, dev, or deploy), the layout choice remains consistent across the CLI, development server, and production builds.

Why Two Layouts Exist

The dual-layout system provides specific architectural benefits:

  • Flexibility for existing codebases – Projects with established src/ directories or complex monorepo structures can adopt Flue without reorganizing their entire file tree. Root-level layout keeps agents accessible alongside existing business logic.
  • Clean separation of concerns – The .flue/ layout creates a dedicated namespace for AI configuration, preventing accidental collisions with existing folders named agents/ or roles/. This isolation mirrors patterns from Next.js, Astro, and other modern frameworks.
  • Deterministic build behavior – By enforcing an exclusive-or rule (.flue wins if present), Flue eliminates ambiguous lookups. The build pipeline never merges files from both locations, ensuring predictable outputs.

Project Structure Examples

Root-Level Layout Example

The examples/hello-world/ directory in the Flue repository demonstrates this approach:


examples/hello-world/
├─ agents/
│   └─ greeter.ts
├─ roles/
│   └─ friendly-assistant.md
└─ flue.config.ts

Flue reads agents from ./agents/ and roles from ./roles/ because no .flue/ folder exists.

.flue Layout Example

The same repository contains hidden .flue/ trees demonstrating the alternative:


examples/hello-world/
├─ .flue/
│   ├─ agents/
│   │   └─ translator.ts
│   └─ roles/
│       └─ code-reviewer.md
└─ flue.config.ts

When the .flue/ directory is present, Flue reads exclusively from .flue/agents/ and .flue/roles/, ignoring any root-level counterparts.

Practical Implementation

Detecting the Source Root Programmatically

To query which layout your project uses at runtime:

import { resolveSourceRoot } from '@flue/sdk/build';

const projectRoot = process.cwd();      // e.g. "/my-project"
const srcRoot = resolveSourceRoot(projectRoot);

console.log('Flue source root:', srcRoot);
// → prints "/my-project/.flue" if that folder exists,
//   otherwise "/my-project"

Source: [build.ts lines 94-98](https://github.com/withastro/flue/blob/main/packages/sdk/src/build.ts#L94-L98)

Discovering Agents Dynamically

Access agent metadata programmatically using the resolved source root:

import { discoverAgents, resolveSourceRoot } from '@flue/sdk/build';

const srcRoot = resolveSourceRoot(process.cwd());
const agents = discoverAgents(srcRoot);

// `agents` is an array of { name, filePath, triggers }
agents.forEach(a => console.log(a.name, a.filePath));

Source: [build.ts lines 106-118](https://github.com/withastro/flue/blob/main/packages/sdk/src/build.ts#L106-L118)

Scaffolding Connectors with Layout Awareness

When generating new connectors, respect the same layout rule used by the CLI:

import * as path from 'path';
import * as fs from 'fs';

const root = process.cwd();
const connectorPath = fs.existsSync(path.join(root, '.flue'))
  ? path.join(root, '.flue', 'connectors', 'daytona.ts')
  : path.join(root, 'connectors', 'daytona.ts');

fs.writeFileSync(connectorPath, `/* Daytona sandbox implementation */`);

Guidance: See the "File location" section of the sandbox connector documentation.

Summary

  • Exclusive layouts – Flue supports either root-level agents//roles/ or .flue/agents//.flue/roles/, never both simultaneously.
  • Automatic detection – The resolveSourceRoot() function in packages/sdk/src/build.ts checks for .flue/ existence and returns that path if found; otherwise, it returns the project root.
  • Propagation – All discovery routines (discoverAgents(), discoverRoles(), discoverAppEntry()) derive paths from the single sourceRoot value, ensuring consistency across the build pipeline.
  • Migration path – You can switch layouts by creating or removing the .flue/ directory, but you must move all Flue-specific files (agents, roles, optional app.ts, connectors) to match the new root.

Frequently Asked Questions

What happens if I have both ./agents/ and ./.flue/agents/?

Flue ignores the root-level ./agents/ directory entirely. The presence of .flue/ triggers unconditional precedence, meaning all discovery operations read exclusively from ./.flue/. You cannot merge or combine files from both locations.

Can I configure Flue to use a custom directory name instead of .flue/?

No. The directory name is hardcoded in resolveSourceRoot() as .flue (lines 94-98 of packages/sdk/src/build.ts). The framework does not expose configuration options to rename this directory or customize the layout detection logic.

Does the layout choice affect my build output directory?

No. The dist/ output directory (or your configured build target) remains independent of your source layout. Only the source resolution changes—resolveSourceRoot() determines where Flue reads agents from, but the compilation target is calculated separately based on your flue.config.ts settings.

Where should I place my custom app.ts entry point?

Place app.ts, app.mts, app.js, or app.mjs inside the active source root. For root-level layouts, put it at ./app.ts; for .flue layouts, place it at ./.flue/app.ts. The discoverAppEntry() function scans the same sourceRoot returned by resolveSourceRoot() when locating your custom application entry.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →