Workflows

Purpose and Scope

Workflows are Flue’s pattern for finite, inspectable operations. Use them when the job has a clear beginning, input contract, terminal result, and event history, such as a background job, document transformation, review task, or CI-oriented automation. This is different from an agent conversation, where work should continue across messages and preserve conversational context. A workflow can still use an agent harness internally, but the reader should think of the workflow itself as a durable run that moves from submitted input to a finished output rather than an ongoing chat surface.

Sources: apps/docs/src/content/docs/guide/workflows.md

A practical workflow gives application code a structured way to ask an AI-backed harness to perform one bounded task. The workflow definition declares the agent that supplies execution policy, optionally declares input and output schemas, and implements the run body that performs the work. Because each invocation becomes its own run, callers can separate admission of work from completion of work. That separation is useful when a route, channel, schedule, or CLI command should start an operation and let Flue track its state and events independently.

Sources: apps/docs/src/content/docs/guide/workflows.md

Relevant Source Files

  • apps/docs/src/content/docs/guide/workflows.md — First-party guide for creating discovered workflows, binding reusable Actions, invoking workflows from the CLI and application code, and opting into HTTP exposure.
  • apps/docs/src/content/docs/sdk/workflows.md — SDK reference for client.workflows.invoke(...), including wait: 'result', WorkflowInvokeOptions, WorkflowInvokeResult, and WorkflowWaitResult.

Core Primitives

A discovered workflow is a module under src/workflows/. The filename becomes the workflow name, and the module’s default export must be the value returned by defineWorkflow(). The definition always includes an agent, which supplies the harness used during execution. In the inline form, the workflow also owns its input, output, and run handler. In the extracted form, the workflow binds an existing Action and lets that Action own the input schema, output schema, and handler contract.

Sources: apps/docs/src/content/docs/guide/workflows.md

The harness is the workflow’s execution environment. Inside an inline run handler, the guide shows creating a session with harness.session() and sending a prompt with session.prompt(input.text). That keeps model interaction behind the same agent-backed harness model used elsewhere in Flue, while the workflow remains a finite operation. Use invoke() for workflow admission from application-owned code, and use dispatch() instead when the caller is continuing a persistent agent conversation rather than starting a bounded workflow run.

Sources: apps/docs/src/content/docs/guide/workflows.md

Define a Workflow

The ordinary authoring path is an inline workflow. Create src/workflows/summarize.ts, import defineAgent and defineWorkflow from @flue/runtime, define validation schemas, and implement run. The example below names the workflow summarize by filename, validates an object containing text, asks the configured model to summarize that text, and returns an object containing summary. This pattern is the right default when the operation is local to a workflow and does not need to be shared as a standalone Action.

Sources: apps/docs/src/content/docs/guide/workflows.md

src/workflows/summarize.ts
import { defineAgent, defineWorkflow } from '@flue/runtime';
import * as v from 'valibot';
 
export default defineWorkflow({
  agent: defineAgent(() => ({ model: 'anthropic/claude-haiku-4-5' })),
  input: v.object({ text: v.string() }),
  output: v.object({ summary: v.string() }),
 
  async run({ harness, input }) {
    const session = await harness.session();
    const response = await session.prompt(input.text);
    return { summary: response.text };
  },
});

When you already have a reusable Action, bind that Action instead of duplicating its contract. In that form, the workflow definition still supplies the agent, but the Action supplies the input, output, and handler. This is useful when the same durable operation should be reused by agents, workflows, or other application code without copying schemas and behavior. The guide presents this as the extracted form: keep the workflow focused on admission and harness policy, and keep reusable operation logic in the Action.

Sources: apps/docs/src/content/docs/guide/workflows.md

src/workflows/summarize.ts
import { defineAgent, defineWorkflow } from '@flue/runtime';
import { summarize } from '../actions/summarize.ts';
 
export default defineWorkflow({
  agent: defineAgent(() => ({ model: 'anthropic/claude-haiku-4-5' })),
  action: summarize,
});

Invoke and Observe Runs

For local development and operational tasks, flue run starts a discovered workflow without requiring authored workflow HTTP exposure. The command accepts JSON through --input, validates that input against the workflow contract, starts the configured Node.js or Cloudflare application temporarily, and invokes the workflow through the existing flue() mount. The normal app.ts pipeline and middleware execute during this temporary runtime, then the command reports run events, prints the successful result as JSON, and exits.

Sources: apps/docs/src/content/docs/guide/workflows.md

pnpm exec flue run summarize --input '{"text":"Flue workflows complete finite operations."}'

From application-owned routes, channels, schedules, or other code that is already executing inside a Flue-built server, use ambient invoke(). Import the exact default export from the discovered workflow module and pass the workflow input. invoke() admits a real workflow run and returns a runId without waiting for completion. That makes it appropriate for places where the caller should enqueue or start work and later observe the run separately rather than blocking the whole application path on the terminal result.

Sources: apps/docs/src/content/docs/guide/workflows.md

import { invoke } from '@flue/runtime';
import summarize from './workflows/summarize.ts';
 
const { runId } = await invoke(summarize, {
  input: { text: 'Summarize this document.' },
});

HTTP and SDK Access

Workflow HTTP access is private by default. A workflow module opts into HTTP behavior with two independent named exports: route and runs. The route export controls invocation at POST /workflows/<name>, while runs controls run records and event streams beneath /runs/<runId>. Keep the authorization policy aligned when the same caller should both start a workflow and inspect the resulting run. If a workflow exposes invocation but not run access, clients may receive a run ID that they cannot inspect over HTTP.

Sources: apps/docs/src/content/docs/guide/workflows.md

The SDK wraps the HTTP invocation contract as client.workflows.invoke(name, options). By default it starts a workflow run and resolves to { runId }. If the workflow also exposes runs middleware, the returned ID can be used with run-oriented SDK APIs to stream events, fetch events, or retrieve run metadata. Pass wait: 'result' when the caller intentionally wants the HTTP request to stay open until the run finishes and resolve with both the run ID and terminal result.

Sources: apps/docs/src/content/docs/sdk/workflows.md

const run = await client.workflows.invoke('summarize', {
  input: { text: 'Summarize this document.' },
});
 
console.log(run.runId);
const run = await client.workflows.invoke('summarize', {
  input: { text: 'Summarize this document.' },
  wait: 'result',
});
 
console.log(run.result);

Compact Reference

SurfaceContractNotes
src/workflows/<name>.tsDiscovered workflow moduleFilename becomes the workflow name.
defineWorkflow({ agent, input, output, run })Inline workflowOwns schemas and handler directly.
defineWorkflow({ agent, action })Action-backed workflowReuses the Action input, output, and handler.
invoke(workflow, { input })Server-side admissionReturns { runId } without waiting for completion.
flue run <name> --input <json>Local CLI invocationStarts a temporary configured application and prints events and result.
export const routeHTTP invocation middlewareExposes POST /workflows/<name>.
export const runsRun-resource middlewareExposes run records and event streams beneath /runs/<runId>.
client.workflows.invoke(name, options)SDK invocationReturns a run ID, or a result when wait: 'result' is used.

Sources: apps/docs/src/content/docs/guide/workflows.md, apps/docs/src/content/docs/sdk/workflows.md

Next Steps

Start by choosing whether the work is finite or conversational. If it is finite, create a file under src/workflows/, define the input and output contract, and test it with pnpm exec flue run. If the operation is shared, extract it into an Action and bind that Action from the workflow. When external callers need access, export both route and runs with matching authentication, then use the SDK to invoke and observe runs from a client application.

Sources: apps/docs/src/content/docs/guide/workflows.md, apps/docs/src/content/docs/sdk/workflows.md