Subflow is a top-level export, unlike the typed components
createSmithers(...) returns (Workflow, Task, Approval, Sandbox,
Signal, Branch, Loop, Parallel): import it directly from
"smthrs" for a child workflow boundary.
The workflow prop takes a SmithersWorkflow value: a workflow file’s
default export. Since smithers((ctx) => ...) returns one, a child workflow
is just another createSmithers module:
WorkflowFileRef instead:
it must default-export a SmithersWorkflow, and its resolved path (including
symlinks) must stay within approvedRoot (default: the parent run’s
rootDir):
smithers(build, { output }) declares which schema
becomes the child’s RunResult.output, the value a parent <Subflow>
persists: pass createSmithers(...).outputs.<key> (a string key or Drizzle
table also works). Without it, the child result reads only from a schema key
literally named output; a child writing elsewhere (like childResult here)
yields an empty subflow result.
Import that value into the parent and pass it to <Subflow workflow={...}>;
the parent declares the schema it persists the result under (key need not
match the child’s, but the shape must):
input you pass via its own ctx.input, typed by giving
the child’s createSmithers an input schema (above); without one,
ctx.input is untyped and needs a guard per field (ctx.input?.repo ?? ".").
Read the persisted subflow result in the parent like a task output:
ctx.outputMaybe(parent.outputs.childSummary, { nodeId: "run-child" }),
where nodeId is the <Subflow id>.
What childRun persists
With mode="childRun" (the default), <Subflow output> stores the child’s
normalized RunResult.output: its last task result, not a table-keyed
snapshot. When the child run finishes, the engine reads the rows written to
its declared result schema (the smithers(build, { output }) argument
above), strips the system columns (runId, nodeId, iteration), and
normalizes:
- One row (the common case: the child’s final task is the sole writer)
unwraps to that plain object, like
{ summary: "..." }above. - Zero rows normalize to
null. - Multiple rows (several tasks or loop iterations writing to the schema) persist as an array of stripped rows.
{ schemaKey: rows } map, validated
against <Subflow output> like a task payload, so
adding or changing the child’s final task changes the parent’s expected
schema: renaming output columns, pointing the result at a different schema, or
adding a writer (one row becomes an array) all change the shape the parent
receives. Update <Subflow output> in the same change, or the subflow task
fails output validation.
Single-file parent and child
For small examples and eval fixtures, define both factories in one.tsx
file with separate createSmithers calls, so the child keeps its own
ctx.input type and output refs, and the parent keeps its own output table
for the subflow result:
input schema is optional but recommended whenever it reads
ctx.input.repo, turning ctx.input from unknown into a typed object; the
child has its own input schema and ctx.input, populated from the parent’s
<Subflow input={...}> prop.
Notes
childRun(default) gives the child its own DB row for retry/cache/resume scope; the persisted result is the child’s last task row, not a table-keyed snapshot (see WhatchildRunpersists).inlinerenders the child tree as siblings in the parent plan, sharing its scope.- Subflows compose; children may contain
<Subflow>themselves. - Declare the child’s result via
smithers(build, { output })unless its schema is namedoutput; that’s what a parent<Subflow>persists.