Documentation Index
Fetch the complete documentation index at: https://smithers.sh/llms.txt
Use this file to discover all available pages before exploring further.
Approval Gate
<Approval> pauses a workflow at an explicit node, waits for a human decision, then continues.
Workflow Definition
/** @jsxImportSource smithers-orchestrator */
// approval-gate.tsx
import {
Approval,
Sequence,
Task,
approvalDecisionSchema,
createSmithers,
} from "smithers-orchestrator";
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const { Workflow, smithers, outputs } = createSmithers({
draft: z.object({
title: z.string(),
content: z.string(),
}),
publishApproval: approvalDecisionSchema,
published: z.object({
url: z.string(),
publishedAt: z.string(),
}),
});
const writer = new Agent({
model: anthropic("claude-sonnet-4-5-20250929"),
instructions:
"You are a technical writer. Draft a blog post with a title and full content based on the given topic.",
});
const publisher = new Agent({
model: anthropic("claude-sonnet-4-5-20250929"),
instructions:
"You are a publishing agent. Take the approved draft and return a URL and timestamp for the published post.",
});
export default smithers((ctx) => {
const draft = ctx.outputMaybe(outputs.draft, { nodeId: "write-draft" });
const decision = ctx.outputMaybe(outputs.publishApproval, {
nodeId: "approve-publish",
});
return (
<Workflow name="approval-gate">
<Sequence>
<Task id="write-draft" output={outputs.draft} agent={writer}>
Write a blog post about deterministic AI workflows and why resumability
matters for production systems.
</Task>
<Approval
id="approve-publish"
output={outputs.publishApproval}
request={{
title: "Publish blog post",
summary: draft
? `Publish "${draft.title}" to the public site.`
: "Publish the current draft.",
}}
/>
{decision?.approved ? (
<Task id="publish" output={outputs.published} agent={publisher}>
Publish this approved draft:{"\n\n"}
Title: {draft?.title}
{"\n\n"}
{draft?.content}
</Task>
) : null}
</Sequence>
</Workflow>
);
});
Running
bunx smithers-orchestrator up approval-gate.tsx --input '{}'
[approval-gate] Starting run mno345
[write-draft] Done -> { title: "Why Resumability Matters", content: "In production AI systems..." }
[approve-publish] Waiting for approval...
[approval-gate] Paused — run `bunx smithers-orchestrator approve` or `bunx smithers-orchestrator deny` to continue.
Approving or Denying
Approve and resume:
bunx smithers-orchestrator approve mno345 --node approve-publish
bunx smithers-orchestrator up approval-gate.tsx --run-id mno345 --resume true
[approve-publish] Approved.
[publish] Running...
[publish] Done -> { url: "https://blog.example.com/resumability", publishedAt: "2026-02-10T12:00:00Z" }
[approval-gate] Completed
Deny and halt:
bunx smithers-orchestrator deny mno345 --node approve-publish
[approve-publish] Denied.
[approval-gate] Halted at node "approve-publish" (denied by user).
Listing Pending Approvals
bunx smithers-orchestrator ps --status waiting-approval
{
"runs": [
{
"id": "mno345",
"workflow": "approval-gate",
"status": "waiting-approval",
"step": "approve-publish",
"started": "2m ago"
}
]
}
How It Works
<Approval> persists a decision object (approved, note, decidedBy, decidedAt) when the workflow resumes. The audit timestamp itself lives in Smithers’ approval records and event log, so decidedAt remains deterministic in durable outputs.
- Re-running after approval replays completed tasks from the database and continues from the approval point.
- Denial is permanent for that run. To retry, start a new run.