The Effect API is the lower-level authoring surface for teams that already model application logic withDocumentation Index
Fetch the complete documentation index at: https://smithers.sh/llms.txt
Use this file to discover all available pages before exploring further.
Effect, Layer, and Schema. It uses the same Smithers
runtime as JSX: steps are persisted in SQLite, completed work is not re-run on
resume, outputs are schema-validated, and dependencies drive scheduling. The
difference is authoring style: every step, approval, sequence, parallel block,
match, branch, loop, worktree, and scope is an ordinary value you can export,
return from a function, or compose with other graph values.
Use JSX for most workflows. Use the Effect API when your workflow lives inside
an Effect service, you want step bodies to return Effect values directly, or
you need a React-free API for generated workflow definitions.
Minimal workflow
Smithers.workflow(opts) returns a typed handle G. Every constructor
(G.step, G.approval, G.sequence, G.parallel, G.match, G.branch,
G.loop, G.worktree, G.scope) returns a graph value. G.from(graph)
finalizes the workflow into something execute-able.
execute() returns an Effect. The success value is the decoded output of the
final graph node: a step output for a single step, the last child for a
sequence, a tuple for a parallel block. If the run stops on an approval or
timer, the success value is the normal RunResult with a waiting status.
Steps and dependencies
Steps are values:input is typed from the workflow’s input schema. The step’s output type is
inferred from the output schema and flows into anything that lists this step
in needs:
Promise, or an
Effect; Smithers decodes the result with the step’s output schema before
writing it.
The step context includes input, dependency values, executionId, stepId,
attempt, iteration, signal, heartbeat(data), and lastHeartbeat.
Control flow
UseG.sequence(...nodes) for ordered work and
G.parallel(...nodes, { maxConcurrency }) for concurrent work. G.parallel
returns a tuple of child results.
G.match(source, { when, then, else }) selects between two statically-known
branches based on a completed step’s output. Both branches are compiled into
the graph; only the matching branch executes.
G.branch({ condition, needs, then, else }) is the same shape but the
predicate runs against an arbitrary needs context, not a single source step.
G.loop({ id, children, until, maxIterations }) repeats a fragment until the
predicate returns true. Nested loops are not supported.
Worktrees
G.worktree({ id, path, branch, skipIf, needs, children }) runs children
inside a git worktree. The worktree is created before the children execute and
torn down afterward.
Reuse
Static reuse is just a graph value:G.scope(instanceId, fragment). The compiler applies
instanceId. as a durable ID prefix to every step and approval inside the
fragment. The same fragment can be mounted under multiple scopes without
collision:
Cross-workflow fragments
For graph fragments that need to live across workflows with different inputs, build them withSmithers.fragment(inputSchema):
Smithers.fragment exposes the same constructors as a workflow handle (step,
approval, sequence, parallel, match, branch, loop, worktree,
scope), but no from — fragments are values, not workflows. Compile happens
when they’re mounted into a real workflow:
Pipe
Every graph value has a.pipe(...fns) method. It’s left-to-right function
application — the same shape as Effect.pipe:
Operational notes
- Provide exactly one persistence layer with
Effect.provide(Smithers.sqlite({ filename })). - Keep step IDs stable across releases; use new IDs for materially different work.
- Use
heartbeat()in long-running steps and honorsignalin external calls. - Use
retry,retryPolicy,timeout,skipIf, andcachethe same way you would on JSX tasks. - Prefer idempotent step bodies. For external side effects, use
executionId,stepId, andattemptwhen constructing idempotency keys. G.matchis graph topology selection — both branches must be statically knowable so durable IDs stay stable across resume. It is not Effect’sMatchmodule (which is runtime value pattern matching).