workflow() DSL
workflow() builds an execution graph from steps declared in the order they run. The compiler connects each step to the one before it, adds a router node for a branch and one node per parallel step, and returns the same CompiledExecutionGraph IR the other builders produce, run by the same GraphRuntime.
Use workflow() when the steps are known and ordered. Use AgentGraph when a node's output decides which node runs next (a routerNode with static edges). Use mission() to build the graph from a goal and a plan template.
Quick Start
import { workflow, LoopController } from '@framers/agentos/orchestration';
import type { WorkflowRuntimeDeps } from '@framers/agentos/orchestration/builders/WorkflowBuilder';
import { z } from 'zod';
// Your application's own tool runner, model call and page fetcher.
declare function runMyTool(name: string, args: Record<string, unknown>): Promise<unknown>;
declare function callMyModel(instructions: string, context: unknown): Promise<string>;
declare function fetchPage(url: string): Promise<string>;
const deps: WorkflowRuntimeDeps = {
toolOrchestrator: {
async processToolCall({ toolCallRequest }) {
return { success: true, output: await runMyTool(toolCallRequest.toolName, toolCallRequest.arguments) };
},
},
loopController: new LoopController(),
async *providerCall(instructions, state) {
const text = await callMyModel(instructions, {
input: state.input,
scratch: state.scratch,
artifacts: state.artifacts,
});
yield { type: 'text_delta', content: text };
return { responseText: text, toolCalls: [], finishReason: 'stop' };
},
// An extension step receives the run's input and scratch.
async extensionExecutor(_extensionId, _method, args) {
const { input } = args as { input: { url: string } };
return { success: true, output: { page: await fetchPage(input.url) } };
},
};
const wf = workflow('summarize-and-tag')
.input(z.object({ url: z.string() }))
.returns(z.object({ summary: z.string(), tags: z.string() }))
.step('fetch', { extension: { extensionId: 'web', method: 'fetchPage' } })
.step('summarize', { gmi: { instructions: 'Summarize the document in 3 sentences.' }, outputAs: 'summary' })
.step('tag', { gmi: { instructions: 'Extract 5 topic tags.' }, outputAs: 'tags' })
.compile({ deps });
const result = await wf.invoke({ url: 'https://example.com/article' });
// { fetch: { page: '...' }, summary: '...', tags: '...' }
deps holds the node executors (WorkflowRuntimeDeps): a tool step needs toolOrchestrator and fails without it; a gmi step needs loopController and providerCall and otherwise succeeds with the output 'gmi-placeholder'; an extension step needs extensionExecutor and otherwise succeeds with the output 'extension-not-configured'. A tool step sends its tool no arguments, so the fetch step above reads the URL as an extension step, whose executor receives { input, scratch }. The Orchestration Guide lists every node type's executor.
Factory Function
workflow(name: string): WorkflowBuilder
Returns a new WorkflowBuilder. The compiled graph's name is name and its id is graph-<name>-<timestamp>; run ids and checkpoint ids are UUIDs.
Schema Declaration
Both .input() and .returns() are required. Compilation throws if either is missing.
workflow('my-pipeline')
.input(z.object({ query: z.string(), limit: z.number() }))
.returns(z.object({ results: z.array(z.string()), count: z.number() }))
The compiler lowers a Zod schema to JSON Schema and stores it in the IR; a plain JSON Schema object is accepted and stored as {}. The runtime does not validate the input or the result against either schema and does not apply Zod defaults. invoke(input) puts input in state.input, and the run returns state.artifacts: each step's output under the step id, or under outputAs when the step sets it.