DromioWorkflow SDK
Workflow SDKAuthoringAPIWorkflow Apps & Harnesses

Design the graph

Decision treeStepsWorkflowsCompositionModels and judgmentHuman boundaries
Design the graph
Decision tree
Steps
Workflows
Composition
Models and judgment
Human boundaries

Composition

An authored workflow can run independently and remain visible when composed inside a parent. Child contracts, waits, events, trace ancestry, failures, and artifacts are preserved rather than flattened into helper calls.

Place one child workflow​

export const clarifyRequest = step.workflow({
id: "requests.clarify",
workflow: clarifyRequestWorkflow,
});

The step inherits the child's input and output contracts. If the child waits, the parent waits and resumes the same child session.

Route to exactly one child​

export const processCapture = step.router({
id: "captures.process",
input: {capture: captureSchema},
routes: {
image: processImageWorkflow,
note: processNoteWorkflow,
},
select: ({input}) => input.capture.type,
mapInput: ({input}, routeId) => ({
source: input.capture.value,
kind: routeId,
}),
});

At least two routes are required. Every route must have compatible input and identical output contracts. Without explicit input and mapInput, the router passes matching keys. The selector must return a declared key; changing the selected route while hydrating a waiting run is rejected.

Fork independent children​

export const reviewResponse = step.fork({
id: "responses.review",
branches: {
assessment: assessResponseWorkflow,
noteAnalysis: analyzeNoteWorkflow,
},
});

At least two branches are required. Each branch receives only its declared input keys. Shared keys must have compatible contracts, branch output keys may not collide, and the current join policy waits for all branches. A branch failure fails the fork.

Iterate a child workflow​

The simple form derives arrays from a child with one input and one output:

export const analyzeAllNotes = step.forEach({
id: "notes.analyze-all",
items: "notes",
collect: "analyses",
workflow: analyzeNoteWorkflow,
});

Use the advanced form for mappings, child configuration, callbacks, and controlled error continuation:

const analyzeAll = step.forEach({
id: "notes.analyze-advanced",
input: {notes: z.array(noteSchema)},
output: {summary: batchSummarySchema},
config: {defaults: {language: "en"}},
prepare: ({config}) => config,
items: ({input}) => input.notes,
childInput: (note) => ({note}),
itemId: (note) => note.id,
itemLabel: (note) => note.title,
workflow: analyzeNoteWorkflow,
workflowConfig: ({prepared}) => prepared,
continueOnError: true,
onItemFailed: ({itemId, error}) => reportFailure(itemId, error),
collect: (results) => ({
summary: {
completed: results.filter((result) => result.status === "completed").length,
failed: results.filter((result) => result.status === "failed").length,
},
}),
});

With continueOnError: false or omitted, an iteration failure fails the step. When true, failed results reach collect() alongside completed outputs.

Use edges for sequence​

Ordinary document edges express ordered dependencies. The target reads values from accumulated state through matching contract keys. Do not use a signal, timer, or nested workflow merely to mean “run this next.”

Choose the shape​

NeedShape
Ordered dependencyDocument edge
Reusable multi-step behaviorstep.workflow()
Assess onceworkflow.judge()
Revise to a quality barworkflow.judgeUntil()
Resolve requirements with a humanworkflow.clarifyUntil()
Exactly one runtime choicestep.router()
Independent concurrent workstep.fork()
Same workflow over a collectionstep.forEach()
Local transformation without its own lifecycleA function inside the owning step

Composition primitives define orchestration semantics; child workflows own their internal behavior. Retain stable child IDs so traces and hydrated runs remain attributable across releases.

Execution kind and semantic role​

Bundle nodes expose both concepts for inspection. Kind controls execution and rendering: model, delegate, gate, question, workflow, or ordinary step. Role explains why a node exists inside a bundle: resolve, judge, gate, ask, merge, or revise. Runtime behavior must never branch on role; changing or removing it does not change validation, persistence, routing, retries, or output.

Bundle nodeExecution kindSemantic role
Contract resolvermodel, delegate, workflow, or stepresolve
Semantic assessormodel, delegate, workflow, or stepjudge
Score policygategate
Human questionquestionask
Accepted-answer transformstepmerge
Candidate or contract revisionAny authored executable kindrevise

Inference ownership changes the assessor, not the workflow semantics:

On this page

  • Workflow as a step
  • Sequential composition
  • Concurrent fork and join
  • Iterate a workflow
  • Choose the right shape
  • Failure boundaries