Action API
Purpose and Scope
The Action API defines the reusable, finite operation primitive in Flue. An Action is application-controlled behavior that can be attached to a workflow or made available to an agent as a model-callable capability. Unlike an open-ended conversation turn, an Action has a named definition, optional typed input, optional typed output, and a finite handler. This makes it useful when a task needs reliable application sequencing, validation, logging, and a clear result while still being able to use the configured agent harness during execution.
Sources: apps/docs/src/content/docs/api/action-api.md
Use this page when you need the public contract for authoring Actions with @flue/runtime. The source documentation says the API is exported from @flue/runtime, and the central entry point is defineAction(). The returned value is a frozen definition that can be bound to a workflow with defineWorkflow({ agent, action }), or exposed through an agent configuration by placing it in the agent's actions field. That placement choice controls who can call the Action: a workflow invocation calls it as the workflow's finite operation, while an agent may expose it to the model as a managed tool-like capability.
Actions occupy the space between tools and workflows. A tool exposes a direct application capability to a model. A workflow structures a durable process around an agent. An Action packages finite logic that can orchestrate an agent harness from application code, including prompting sessions, using configured capabilities, and returning a validated result. The Action owns its schema and handler, so the same definition can be reused from several workflows or agent configurations without duplicating validation rules or model-facing metadata.
Relevant Source Files
apps/docs/src/content/docs/api/action-api.md— First-party API reference fordefineAction(),ActionContext, input and output validation, utility types, and Action integration points.
Public Entry Point
defineAction() is generic over input and output and accepts an ActionOptions object. It returns an ActionDefinition, which is the reusable value passed to workflow and agent configuration APIs. The visible signature is intentionally small: the behavior is described by the options object, not by subclassing or imperative registration. This style keeps Action definitions portable, importable, and suitable for colocating near the domain logic they perform.
Sources: apps/docs/src/content/docs/api/action-api.md
function defineAction<TInput, TOutput>(
options: ActionOptions<TInput, TOutput>,
): ActionDefinition<TInput, TOutput>;The options object has five meaningful fields. name is required and must be a non-empty model-facing tool name. It must not conflict with another active tool or Action name, because an agent needs a single unambiguous callable name when multiple capabilities are available. description is also required and must be non-empty, giving the model-facing explanation of when the Action should be used. input is optional, but when present it must be a top-level object Valibot schema. output is optional and may be any Valibot schema for the returned value. run is required and performs the finite behavior with an Action context.
| Field | Required | Contract |
|---|---|---|
name | Yes | Non-empty model-facing tool name; must not conflict with another active tool or Action name. |
description | Yes | Non-empty model-facing description. |
input | No | Top-level object Valibot schema. |
output | No | Valibot schema for the returned value. |
run | Yes | Finite handler receiving ActionContext. |
Definition-time validation is part of the public contract. The API rejects missing metadata, schemas that are not Valibot schemas, and input schemas whose top level is not an object. The docs also call out an important consistency rule: inline workflow definitions written as defineWorkflow({ run }) delegate these schema checks to defineAction() and report the same errors. That means authors can learn a single validation model for finite workflow-like operations, whether they define a standalone reusable Action or use the inline workflow shorthand.
ActionContext
The Action handler receives ActionContext, which is deliberately scoped to invocation-time capabilities rather than transport or platform details. The context always includes harness and log. When an input schema is declared, the context type also includes input, and that value is the parsed and transformed output of the schema rather than the raw caller payload. When no input schema is declared, the input member is omitted from the type. This prevents handlers from accidentally depending on unvalidated caller data.
Sources: apps/docs/src/content/docs/api/action-api.md
type ActionContext<S> = {
readonly harness: FlueHarness;
readonly log: FlueLogger;
} & (S extends ActionInputSchema ? { readonly input: InferOutput<S> } : {});The harness member is the invocation-scoped Flue harness supplied by the runner. In practical terms, this is the object an Action uses to open sessions and work through the agent configuration that was bound to the invocation. The log member is the structured logger for the current execution, so handlers should use it for progress and diagnostic messages instead of relying on ad hoc console output. Together, those members make Actions testable and runtime-aware without coupling them to HTTP requests, channel payloads, or deployment-specific bindings.
The context intentionally excludes transport requests, platform bindings, and workflow identity. That exclusion is a design constraint, not a gap. If an Action needs data from a request, channel event, or workflow caller, pass the data through the Action input. If it needs platform capabilities, configure those capabilities on the agent or surrounding application and access them through the harness-mediated runtime. This keeps the Action definition reusable across routes, workflows, and model calls, and it prevents hidden dependencies on whichever transport happened to trigger the run.
Input and Output Contracts
Input validation happens before run() executes. If the Action declares an input schema, Flue validates the caller-provided value and applies Valibot transformations before the handler receives it. The handler therefore operates on the parsed output type, not an unchecked payload. This matters for Actions because they often perform reliability-sensitive work: the boundary between the caller and the finite operation is validated before any application-defined steps, prompts, or side effects begin.
Sources: apps/docs/src/content/docs/api/action-api.md
Output validation happens after run() when an output schema exists. The returned value is parsed against the schema, and Valibot transformations are reflected in the value treated as the Action result. If no output schema is declared, the Action may return any JSON-serializable value or undefined. If an output schema is declared, the parsed result must be JSON-serializable and cannot be undefined unless the schema itself produces a serializable value. This gives authors a clear choice between flexible ad hoc return values and a strongly described result contract.
The top-level object requirement for input schemas is especially important when exposing an Action to a model. A model-callable capability needs named arguments that can be described, validated, and routed predictably. Requiring an object-shaped input avoids ambiguous single primitive payloads and makes Action input resemble structured tool arguments. Output schemas have more flexibility because they describe the final result, but the runtime still requires the result to be serializable so it can be recorded, streamed, or returned by the durable execution machinery.
Integration with Agents and Workflows
An Action becomes useful when it is imported into an agent or workflow definition. Bound to a workflow, it supplies the finite operation that runs with the workflow's configured agent. Exposed through an agent's actions field, it becomes available to the model alongside other active capabilities. In both cases, the Action definition carries its own name, description, schemas, and handler, so the integration point only needs to reference the definition rather than restating its contract.
Sources: apps/docs/src/content/docs/api/action-api.md
import { defineAction } from '@flue/runtime';
import * as v from 'valibot';
export const summarize = defineAction({
name: 'summarize_document',
description: 'Summarize a document clearly and concisely.',
input: v.object({ text: v.string() }),
output: v.object({ summary: v.string() }),
async run({ harness, input, log }) {
log.info('Summarizing document');
const session = await harness.session();
const response = await session.prompt(`Summarize this text:\n\n${input.text}`);
return { summary: response.text };
},
});When a model calls an Action, Flue runs it in an isolated child scope. The child shares the parent agent configuration, sandbox, and filesystem, which means it can use the same configured environment to do real work. At the same time, it has independent default and named sessions and cannot reenter the active parent session. This separation protects the parent conversation from recursive session access while still allowing the Action to use the same harness configuration. The canonical records remain append-only in the agent-instance stream for the lifetime of that instance, and there is no recursive per-session deletion behavior.
This isolation model is a useful mental model for choosing between an Action and a plain tool. If the capability is a direct application call, a tool may be enough. If the capability needs a controlled sequence that can open its own sessions, prompt the model, log progress, validate a result, and be reused as workflow behavior, define an Action. The Action remains finite because the handler returns a result, but it can still drive harness-mediated work inside its execution scope.
Utility Types and Type-Level Contracts
The API reference exposes utility types for deriving input and output types from an Action definition. ActionInput<TAction> represents the authored schema input type. ActionOutput<TAction> represents the parsed output type, or unknown when no output schema is declared. These helpers are useful when application code needs to type a caller, test fixture, route adapter, or workflow wrapper from the Action definition itself rather than duplicating schema-derived TypeScript types by hand.
Sources: apps/docs/src/content/docs/api/action-api.md
type ActionInput<TAction extends ActionDefinition> = /* schema input type */;
type ActionOutput<TAction extends ActionDefinition> = /* schema output type */;
type ActionInputSchema = GenericSchema<Record<string, unknown>, unknown>;
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };ActionInputSchema captures the top-level object-schema shape accepted for Action inputs. JsonValue documents the serializable value space that Action results must fit when returned or validated. Together, these types reinforce the same runtime rules described earlier: inputs are structured objects, outputs must be serializable, and schema transformations are part of the typed contract. Prefer deriving types from the Action definition when possible, because it keeps the public caller contract aligned with the schema and reduces the chance of stale hand-written interfaces.
Implementation Notes for Authors
Start an Action definition by naming the operation from the model's perspective. The name should describe a single callable capability and avoid collisions with tools or other Actions active on the same agent. Then write the description as selection guidance for the model or workflow author, not as an implementation comment. A good description helps an agent decide when the finite operation is appropriate, while the handler contains the actual operational steps.
Next, decide whether the Action needs input and output schemas. Use input whenever the handler depends on caller-provided data, even if the shape is simple. Use output when downstream code, workflow callers, or clients need a predictable result. If the result is only an internal side effect or log-producing operation, omitting output can be acceptable, but the returned value still needs to be JSON-serializable unless it is undefined. For reliability-sensitive tasks, an explicit output schema usually makes failures easier to detect and reason about.
Finally, keep transport-specific assumptions out of the handler. Because ActionContext does not include requests, platform bindings, or workflow identity, the clean pattern is to pass required facts through validated input and rely on the configured agent harness for runtime capabilities. That discipline makes Actions portable across local runs, deployed routes, agent model calls, and workflows. For deeper integration details, read the Agent API for the actions field, the Workflow API for binding an Action to a workflow, and the Errors Reference for the full error taxonomy.
Related Pages
- Agent API — for exposing Action definitions through an agent's
actionsfield. - Workflow API — for binding an Action to
defineWorkflow({ agent, action }). - Tools — for deciding when a direct model-callable tool is simpler than a finite Action.
- Errors Reference — for detailed runtime and validation error behavior beyond the visible Action API contract.