The Core Insight
Here is a workflow with two tasks, except the second one doesn’t exist yet:analysis is undefined. Only analyze is mounted. The plan has one task.
Frame 2 (after analyze completes): analysis has a value. Both tasks are mounted. The plan grew.
Read that again. The second task doesn’t just “wait” — it literally does not exist in the execution plan until the first task produces a result. The plan is not static. It unfolds over time as each render cycle reveals new tasks.
This is the key insight: your workflow is a function of its own outputs.
The Render-Schedule-Execute Loop
Every Smithers run follows this cycle:ctx.outputMaybe() now returns a value it didn’t before, new tasks may mount. The engine detects this by comparing the set of mounted task IDs between frames and continues the loop.
Why does this matter? Because tasks appear in the plan at the moment their preconditions are met — not a moment before. You never declare “task B depends on task A.” You write a conditional, and the dependency emerges from the render.
React as a Workflow Compiler
You might be wondering: why React? Why not just a function that returns a list of tasks? Because React gives you a reconciler — a well-tested engine for turning a tree of declarations into a structured result. Smithers implements a custom React reconciler that produces an in-memoryHostElement tree instead of DOM nodes. The reconciler is used purely for tree construction:
React.createElement()calls build the component tree- The reconciler resolves props, children, and conditional branches
- The result is walked to extract
TaskDescriptorobjects - Those descriptors drive scheduling and execution
Conditional Mounting
Standard JSX conditions control which tasks exist in the plan:Component Composition
Here’s where it gets interesting. Large workflows decompose into reusable components — the same way large UIs do:<ReviewCycle> is a self-contained workflow fragment with its own loop, its own state lookups, and its own conditional logic. This is standard React composition — but it’s building an execution plan, not a UI.
Dynamic Task Generation
Because JSX is just JavaScript, you can generate tasks from runtime data:Custom Hooks
Smithers providesuseCtx() — a React hook that returns the workflow context. If you’ve written custom hooks before, you already know what to do. Build on top of it to extract common patterns.
Extracting Output Logic
Encapsulating Iteration Patterns
Conditional Workflow Fragments
React Patterns That Work
Because Smithers uses a real React reconciler, many standard React patterns work as-is. If you’ve used them in a UI, you can use them in a workflow.Props and Children
Render Props
Higher-Order Components
React Context for Configuration
What Doesn’t Apply
Now for the part that might trip you up. Smithers renders the JSX tree fresh on each frame with a newctx. There is no persistent component state between frames. So some React features you’re used to simply don’t apply:
| React feature | Works in Smithers? | Why |
|---|---|---|
| Component composition | Yes | Tree construction |
useContext / custom context | Yes | Available during render |
useCtx() and custom hooks | Yes | Read from workflow context |
| Conditional rendering | Yes | Controls plan evolution |
React.memo | No effect | Each frame is fresh |
useState | No effect | No persistent state between frames |
useEffect | No effect | No mount/unmount lifecycle |
useRef | No effect | Refs reset each frame |
ctx object is the single source of truth during each render, and it’s built from persisted outputs. If you find yourself reaching for useState, stop — the answer is almost certainly ctx.outputMaybe() or ctx.latest().
Why This Matters
So why go through all this? Because the reactive model gives Smithers three properties that static DAG definitions cannot provide.1. Plans That Adapt
The workflow plan is not fixed upfront. It evolves as tasks complete:2. Natural Data Dependencies
Instead of declaring edges between nodes in a graph DSL, data dependencies are expressed as normal JavaScript:if statement, you can express a dependency.
3. Reuse Through Composition
React’s component model means workflow logic is reusable without special framework support:Next Steps
- Workflows Overview — The big picture of what workflows are and how they work.
- Workflow State — The ctx API and how data flows between tasks.
- Control Flow — The four control-flow primitives that structure execution.