needsApproval: true flag onto a task and let the scheduler figure it out. But think about what that actually means. The approval isn’t a property of the task — it’s a separate decision with its own lifecycle, its own persistence requirements, and its own downstream consequences. Treating it as a boolean flag hides all of that.
Approvals should be explicit workflow nodes, not a boolean flag on an otherwise normal step.
From first principles, an approval is:
- a durable request for a human decision
- a suspended execution state
- an audited decision record
- a dependency that downstream work can wait on
Approval as a Node
Instead of:Approval node is right there in the graph, visible to anyone reading the workflow. It has an id, an output type, a request, and an explicit denial policy. Nothing is hidden.
This is better for three reasons:
- the approval is visible in the graph
- the execution can suspend on a first-class durable node
- the decision can be reused or branched on explicitly
What an Approval Produces
An approval gate should resolve to a typed decision object:decidedBy matter in the type?” Because six months from now, when someone asks who approved the deploy that broke prod, you want the answer in the workflow output — not buried in a Slack thread.
Lifecycle
An approval node should move through a durable lifecycle:- Smithers reaches the approval node.
- It persists an approval request record.
- The execution suspends in a durable waiting state.
- A human approves or denies the request.
- The node resolves according to its policy.
- Downstream nodes become eligible to run.
Request Shape
Therequest function should be pure and derived from already-computed workflow data:
Denial Policies
What should happen when someone says “no”? That depends entirely on context. A compliance gate should halt the workflow. A review gate might just record the rejection and move on. An approval gate should make denial behavior explicit.onDeny: "fail"
The workflow fails when the gate is denied.
This is appropriate for destructive or compliance-sensitive actions.
onDeny: "continue"
The gate resolves to a denial decision and the workflow continues. Downstream logic can branch on that value.
onDeny: "skip"
The protected branch is skipped, but the rest of the workflow continues.
The important part is that denial handling is declared in the workflow, not buried inside scheduler heuristics. You read the workflow definition and know exactly what “denied” means for each gate.
Branching on Approval
Because approvals are values, not only control flags, you can route explicitly:Branch. If approved, publish. If denied, record the rejection. Both paths are visible in the workflow definition, both produce typed outputs, both are part of the graph. No hidden conditional logic in the scheduler.
Storage Model
Approval state belongs to Smithers-managed workflow metadata, not to domain models. Smithers should persist at least:- approval node id
- execution id
- current status
- request payload
- decision payload
- timestamps
- actor identity
- resumability
- auditability
- UI/API query support
- explicit graph semantics
Approvals and Effect Primitives
Approvals map naturally onto durable Effect concepts:- a request record is persisted metadata
- the waiting state is a durable suspension point
- the decision behaves like a durable deferred value
Notifications and Automation
An approval sitting in durable storage is useless if nobody knows about it. Approval creation should emit a durable event so other systems can react:- send Slack messages
- open a review UI
- create a Linear issue
- notify on-call engineers
- Smithers records the approval request durably
- external systems subscribe and notify
- the human decision flows back into the same durable gate
CLI and API Shape
The control plane should target approval nodes directly. For example:(runId, nodeId), not a hidden internal row id.
Simple, direct, auditable. The run ID tells you which execution. The node ID tells you which gate. The note tells you why. That’s everything you need.
Why Explicit Gates Matter
An explicit approval node is easier to reason about than a property on a task because it makes the workflow honest. You can see:- where human intervention is required
- what exactly is being approved
- how denial changes the graph
- what downstream work depends on the decision
Approval node, you know immediately: the workflow pauses here for a human. You know what data the human sees. You know what happens on “yes” and what happens on “no.” There’s nothing to guess.
Next Steps
- Caching — See how reusable outputs and approval-gated work interact.
- Execution Model — Understand how suspended approval nodes fit into durable execution.
- Runtime Events — This page will need to describe approval events in the new model.