# Flue createAgent & Agent/Workflow Split

> How Flue authors agents as TypeScript modules with createAgent, defineAgentProfile, and explicit skill/tool imports; how workflows export run(ctx) for finite executions; and how the CLI build graph discovers agents/ and workflows/ layouts.

- Repository: vercel/eve-with-withastro-flue
- GitHub: https://github.com/vercel/eve
- Human wiki: https://www.grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681
- Complete Markdown: https://www.grok-wiki.com/public/wiki/vercel-eve-with-withastro-flue-43b600348681/llms-full.txt

## Source Files

- `withastro-flue:packages/runtime/src/agent-definition.ts`
- `withastro-flue:packages/runtime/src/index.ts`
- `withastro-flue:examples/hello-world/src/agents/session-test.ts`
- `withastro-flue:examples/sentry/src/workflows/hello.ts`
- `withastro-flue:CHANGELOG.md`
- `withastro-flue:apps/www/src/pages/start.md.ts`
- `withastro-flue:packages/cli/package.json`

---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [withastro-flue/packages/runtime/src/agent-definition.ts](withastro-flue/packages/runtime/src/agent-definition.ts)
- [withastro-flue/packages/runtime/src/index.ts](withastro-flue/packages/runtime/src/index.ts)
- [withastro-flue/packages/runtime/src/types.ts](withastro-flue/packages/runtime/src/types.ts)
- [withastro-flue/packages/cli/src/lib/build.ts](withastro-flue/packages/cli/src/lib/build.ts)
- [withastro-flue/packages/cli/src/lib/source-root.ts](withastro-flue/packages/cli/src/lib/source-root.ts)
- [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts](withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts)
- [withastro-flue/packages/cli/src/lib/build-plugin-node.ts](withastro-flue/packages/cli/src/lib/build-plugin-node.ts)
- [withastro-flue/packages/cli/src/lib/config.ts](withastro-flue/packages/cli/src/lib/config.ts)
- [withastro-flue/examples/hello-world/src/agents/session-test.ts](withastro-flue/examples/hello-world/src/agents/session-test.ts)
- [withastro-flue/examples/hello-world/src/workflows/hello.ts](withastro-flue/examples/hello-world/src/workflows/hello.ts)
- [withastro-flue/examples/sentry/src/workflows/hello.ts](withastro-flue/examples/sentry/src/workflows/hello.ts)
- [withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts](withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts)
- [withastro-flue/examples/chat-sdk/src/agents/assistant.ts](withastro-flue/examples/chat-sdk/src/agents/assistant.ts)
- [withastro-flue/AGENTS.md](withastro-flue/AGENTS.md)
- [withastro-flue/CHANGELOG.md](withastro-flue/CHANGELOG.md)
- [withastro-flue/apps/www/src/pages/start.md.ts](withastro-flue/apps/www/src/pages/start.md.ts)

</details>

# Flue createAgent & Agent/Workflow Split

Flue separates **persistent agents** from **finite workflows** at the file-system and runtime level. Agents are TypeScript modules that default-export a `createAgent(...)` initializer; workflows are separate modules that export a `run(ctx)` function for bounded jobs. The CLI discovers both layouts, feeds them through a shared Vite build graph, and generates a server entry that wires addressable agents and invocable workflows into one deployable artifact.

This split matters because the two shapes solve different problems: agents keep sessions across direct HTTP prompts, WebSocket conversations, and `dispatch(...)` deliveries, while workflows own persisted **runs** with a start, a result, and an event stream. Mixing them — for example, putting one-shot jobs in `agents/` — breaks discovery, routing, and run semantics.

## Agents vs workflows

| Dimension | Agent module (`agents/<name>.ts`) | Workflow module (`workflows/<name>.ts`) |
|-----------|-----------------------------------|------------------------------------------|
| Export contract | Default-export `createAgent(...)` | Export callable `run(ctx)` |
| Identity | Addressed by module basename (`session-test` → `/agents/session-test/:id`) | Addressed by workflow name; each invocation gets a `run_<ulid>` |
| Lifetime | Persistent instance with harnesses and sessions | Finite execution; `ctx.id` is the run id |
| Typical use | Chat assistants, support bots, event-driven triage | Summarize a ticket, generate a report, CI/batch jobs |
| Agent initialization | Runtime calls `initialize` on direct interaction or dispatch | Workflow calls `ctx.init(createdAgent)` when it needs a harness |
| Optional HTTP surface | `export const route` middleware | `export const route` middleware |

Sources: [withastro-flue/AGENTS.md:7-18](), [withastro-flue/CHANGELOG.md:230-238]()

The terminology stack is explicit in project docs: a **profile** (`defineAgentProfile`) is reusable configuration; a **created agent** (`createAgent`) is the runtime initializer; an **agent module** default-exports that created agent; workflows initialize created agents locally via `init(agent)` rather than re-declaring inline config.

```text
agents/<name>.ts          workflows/<name>.ts
       │                          │
       ▼                          ▼
default-export            export async function run(ctx)
createAgent(...)                    │
       │                    ctx.init(agent) ──► FlueHarness
       │                          │                  │
       ▼                          ▼                  ▼
addressable instance         finite run         session.prompt/skill/task
(sessions persist)          (result + stream)
```

## Authoring agents with `createAgent` and `defineAgentProfile`

### `createAgent` — the module contract

`createAgent` wraps an initializer function and returns a frozen `CreatedAgent` marker object (`__flueCreatedAgent: true`). The initializer runs whenever the runtime prepares a harness — both for addressable agent interactions and when a workflow calls `ctx.init()`. It is **not** a one-time constructor for a persistent instance id.

```typescript
// withastro-flue/examples/hello-world/src/agents/session-test.ts
const sessionTest = defineAgentProfile({
  instructions: 'You are a test agent for session-oriented message delivery.',
});

export default createAgent(() => ({ profile: sessionTest }));
```

Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:60-80](), [withastro-flue/examples/hello-world/src/agents/session-test.ts:1-9]()

Key rules enforced at authoring time:

- Agent modules **must** default-export `createAgent(...)`; the build normalizer rejects anything else.
- The initializer must return an `AgentRuntimeConfig` with known fields (`model`, `instructions`, `profile`, `skills`, `tools`, `subagents`, `sandbox`, etc.).
- Agent names come from the **module filename**, not a top-level `name` on the runtime config (`name` is profile-only for subagents).

### `defineAgentProfile` — reusable baselines

`defineAgentProfile` validates and returns a reusable `AgentProfile` object. Use it to share instructions, model defaults, skills, tools, and subagents across a created agent or as named subagents for `session.task()`.

Validation is strict: unknown fields, duplicate capability names, invalid thinking levels, and circular subagent graphs all throw at definition time via Valibot schemas.

Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:48-57](), [withastro-flue/packages/runtime/src/agent-definition.ts:21-38]()

### Explicit skill and tool imports

Flue does not auto-discover arbitrary npm skills at agent definition time for statically bundled capabilities. Authors import skills and tools explicitly:

| Capability | Import / declaration | Where it attaches |
|------------|---------------------|-------------------|
| Packaged skill | `import review from '../skills/review/SKILL.md' with { type: 'skill' }` | `createAgent(() => ({ skills: [review] }))` |
| Custom tool | `defineTool({ name, description, parameters, execute })` | Agent config `tools: [...]` or `session.prompt(..., { tools })` |
| Named subagent | `defineAgentProfile({ name: 'greeter', ... })` | `subagents: [greeter]` on agent config |
| Runtime workspace skill | `AGENTS.md` / `.agents/skills/` | Discovered from session cwd at runtime (not build graph) |

Packaged `SKILL.md` imports flow through the Vite graph and land in `virtual:flue/packaged-skills` at build time. Runtime workspace skills are a separate path — the build comment in `build.ts` notes they are discovered from session cwd, not from static module imports.

```typescript
// withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts
import review from '../skills/review/SKILL.md' with { type: 'skill' };

const agent = createAgent(() => ({ model: 'anthropic/claude-haiku-4-5', skills: [review] }));
```

```typescript
// withastro-flue/examples/chat-sdk/src/agents/assistant.ts (tools on agent config)
tools: [
  defineTool({
    name: 'reply_to_chat_thread',
    description: 'Post a response into the originating Chat SDK thread.',
    parameters: v.object({ threadId: v.string(), text: v.string() }),
    execute: async ({ threadId, text }) => { /* ... */ },
  }),
],
```

Sources: [withastro-flue/examples/imported-skill/src/workflows/with-imported-skill.ts:1-13](), [withastro-flue/examples/chat-sdk/src/agents/assistant.ts:41-54](), [withastro-flue/packages/cli/src/lib/build.ts:36-37](), [withastro-flue/packages/cli/src/lib/build-plugin-node.ts:69-70]()

`defineTool` parameters use Valibot schemas (not TypeBox); `defineAgentProfile` and `createAgent` both validate tool definitions require `name`, `description`, `parameters`, and `execute`.

Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:234-251](), [withastro-flue/CHANGELOG.md:17-18]()

## Authoring workflows with `run(ctx)`

Workflow modules export an async `run` function receiving `FlueContext`. The context supplies the run id, typed payload, environment bindings, optional HTTP `req`, structured `log`, and `init(agent)` for harness creation.

### Finite execution pattern

Workflows return a result when `run` completes. They do not default-export anything — the build normalizer requires `typeof mod.run === 'function'`.

```typescript
// withastro-flue/examples/sentry/src/workflows/hello.ts
export async function run(ctx: FlueContext) {
  ctx.log.info('hello workflow starting', { instanceId: ctx.id });
  return { greeting: 'hello from flue', id: ctx.id };
}
```

A workflow that needs model calls initializes a **local** created agent (which may be defined inline in the same file or imported from an agent module):

```typescript
// withastro-flue/examples/hello-world/src/workflows/hello.ts
const agent = createAgent(() => ({ model: 'anthropic/claude-sonnet-4-6' }));

export async function run({ init, log }: FlueContext) {
  const harness = await init(agent);
  const session = await harness.session();
  const response = await session.prompt('What is 2 + 2? Return only the number.', {
    result: v.object({ answer: v.number() }),
  });
  return response.data;
}
```

Sources: [withastro-flue/examples/sentry/src/workflows/hello.ts:22-28](), [withastro-flue/examples/hello-world/src/workflows/hello.ts:6-34](), [withastro-flue/packages/runtime/src/types.ts:431-466]()

### When to add a workflow

Project scaffolding guidance recommends **agent only** for continuing assistants and **agent + workflow** only when a bounded, result-oriented job is needed. Workflows are not required to test agents — `flue connect <agent-name> local` suffices for local interaction.

Sources: [withastro-flue/apps/www/src/pages/start.md.ts:18-37](), [withastro-flue/apps/www/src/pages/start.md.ts:79-83]()

Optional `export const route: WorkflowRouteHandler` (or `AgentRouteHandler` on agents) declares HTTP middleware; without it, the module is still discovered but lacks a direct HTTP transport in the manifest.

Sources: [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts:48-56]()

## CLI build graph: discovering `agents/` and `workflows/`

### Source root resolution

The CLI picks exactly one source directory and never mixes layouts:

1. `<root>/.flue/` if it exists as a directory
2. else `<root>/src/` if it exists
3. else `<root>` (root layout)

Within the chosen `sourceRoot`, modules live in immediate child folders `agents/`, `workflows/`, and `channels/`.

```typescript
// withastro-flue/packages/cli/src/lib/source-root.ts
for (const sourceDirectory of ['.flue', 'src']) {
  const candidate = path.join(root, sourceDirectory);
  if (fs.statSync(candidate).isDirectory()) return candidate;
}
return root;
```

Sources: [withastro-flue/packages/cli/src/lib/source-root.ts:4-11](), [withastro-flue/packages/cli/src/lib/config.ts:37-39](), [withastro-flue/apps/www/src/pages/start.md.ts:39-45]()

### Module discovery

`discoverAgents` and `discoverWorkflows` scan `sourceRoot/agents/` and `sourceRoot/workflows/` respectively:

- Accept `.ts`, `.js`, `.mts`, `.mjs` files; skip `.d.ts` / `.d.mts` declaration files
- Derive the module **name** from the basename (e.g. `hello.ts` → `hello`)
- Reject duplicate basenames and invalid names (`:` forbidden in agent/channel names)
- Require at least one agent or workflow file; otherwise build throws

Sources: [withastro-flue/packages/cli/src/lib/build.ts:62-67](), [withastro-flue/packages/cli/src/lib/build.ts:98-103](), [withastro-flue/packages/cli/src/lib/build.ts:287-316]()

### Generated entry and Vite graph

The build pipeline:

1. Resolves `sourceRoot` from project `root`
2. Discovers agents, workflows, channels, and optional `app.ts` / `db.ts` / `cloudflare.ts` entries
3. Delegates to a target plugin (`NodePlugin` or `CloudflarePlugin`) to `generateEntryPoint`
4. Emits imports for every discovered module into `<root>/.flue-vite/_entry.ts`
5. Calls `normalizeBuiltModules` to validate exports and build the runtime manifest
6. Bundles through Vite (packaging statically imported skills via `virtual:flue/packaged-skills`)

```mermaid
flowchart TB
  subgraph Authoring["Authoring (one sourceRoot)"]
    AR[".flue/ or src/ or root"]
    A["agents/*.ts<br/>default-export createAgent"]
    W["workflows/*.ts<br/>export run(ctx)"]
    AR --> A
    AR --> W
  end

  subgraph CLI["@flue/cli build"]
    SR["resolveSourceRoot(root)"]
    DA["discoverAgents(sourceRoot)"]
    DW["discoverWorkflows(sourceRoot)"]
    GE["generateEntryPoint → .flue-vite/_entry.ts"]
    NM["normalizeBuiltModules()"]
    VT["Vite bundle → dist/"]
    SR --> DA
    SR --> DW
    DA --> GE
    DW --> GE
    GE --> NM
    NM --> VT
  end

  subgraph Runtime["@flue/runtime server"]
    CA["createdAgents[name]"]
    WH["workflowHandlers[name]"]
    MF["manifest.agents / manifest.workflows"]
    NM --> CA
    NM --> WH
    NM --> MF
  end

  A --> DA
  W --> DW
```

Sources: [withastro-flue/packages/cli/src/lib/build-plugin-node.ts:14-112](), [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts:21-57]()

### Normalization contract (build-time enforcement)

| Module kind | Required export | Optional exports | Build-time error if violated |
|-------------|----------------|------------------|------------------------------|
| Agent | `default`: `createAgent(...)` with `__flueCreatedAgent` | `route`, `description` | Missing default; duplicate `createAgent` value across modules |
| Workflow | `run` function | `route` | `run` not callable |
| Channel | named `channel` binding with routes | — | (separate discovery path) |

The normalizer registers `createdAgents[name] = mod.default` for dispatchable agents and `localWorkflowHandlers[name] = mod.run` for all workflows. HTTP-exposed workflows additionally populate `workflowHandlers` when `route` is exported.

Sources: [withastro-flue/packages/cli/src/lib/generated-entry-normalization.ts:32-57]()

### Dev server reload scope

The dev watcher treats changes under `agents/`, `workflows/`, and related source paths as rebuild triggers, keeping the generated entry in sync with the discovered module set.

Sources: [withastro-flue/packages/cli/src/lib/dev.ts:628-629]()

## Runtime initialization: two paths, one `createAgent`

```mermaid
sequenceDiagram
  participant AM as agents/hello.ts
  participant WM as workflows/job.ts
  participant RT as @flue/runtime
  participant CA as createAgent initializer

  Note over AM,RT: Addressable agent path
  RT->>AM: import default created agent
  RT->>CA: initialize({ id, env, payload: undefined })
  CA-->>RT: AgentRuntimeConfig
  RT->>RT: harness.session() → persistent sessions

  Note over WM,RT: Workflow path
  RT->>WM: invoke run(ctx)
  WM->>RT: ctx.init(createdAgent)
  RT->>CA: initialize({ id: runId, env, payload })
  CA-->>RT: AgentRuntimeConfig
  RT-->>WM: FlueHarness
  WM->>WM: session operations → return result
```

Both paths call the same `initialize` function on the `CreatedAgent`. The difference is **who owns the lifecycle**: the runtime owns addressable agent instances; the workflow owns the run and decides when to call `init`.

Sources: [withastro-flue/packages/runtime/src/agent-definition.ts:65-70](), [withastro-flue/packages/runtime/src/types.ts:60-67](), [withastro-flue/packages/runtime/src/types.ts:462-466]()

## Practical authoring checklist

1. Pick one source layout (`.flue`, `src`, or root) and place modules only there.
2. Create `agents/<name>.ts` with `export default createAgent(() => ({ ... }))`.
3. Extract reusable config into `defineAgentProfile` when sharing across agents or subagents.
4. Import skills (`SKILL.md` with `{ type: 'skill' }`) and tools (`defineTool`) explicitly in the initializer or per-session `prompt` options.
5. Add `workflows/<name>.ts` with `export async function run(ctx)` only for bounded jobs; call `await ctx.init(agent)` inside.
6. Run `flue build` — the CLI lists discovered agent and workflow names before bundling.
7. Test agents locally with `flue connect`; invoke workflows via `POST /workflows/<name>`.

Sources: [withastro-flue/apps/www/src/pages/start.md.ts:71-103](), [withastro-flue/packages/cli/src/lib/build.ts:83-94]()

## Summary

Flue's agent/workflow split is enforced at three layers: **file layout** (`agents/` vs `workflows/`), **export contracts** (`default createAgent` vs `export run`), and **runtime semantics** (persistent instances and sessions vs finite runs with results). `createAgent` and `defineAgentProfile` are the composable authoring primitives; skills and tools are wired through explicit imports and validated definitions. The `@flue/cli` build discovers modules from a single resolved source root, generates a normalized entry point, and bundles everything through Vite into a deployable server that exposes both addressable agents and invocable workflows from one manifest.
