<Task> but the human is the agent. The workflow suspends until a human provides JSON input matching the output schema. If the input fails validation, the human gets up to maxAttempts retries (default 10).
Import
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | (required) | Unique node id within the workflow. |
output | z.ZodObject | Table | string | (required) | Where to store the human’s response. |
outputSchema | z.ZodObject | undefined | Zod schema the human must conform to. Inferred from output when it is a Zod schema. |
prompt | string | ReactNode | (required) | Instructions shown to the human. |
maxAttempts | number | 10 | Max validation retries before failure. |
async | boolean | false | When true, unrelated downstream flow can continue while the human response is still pending. Explicit dependencies still wait for the validated output. |
skipIf | boolean | false | Skip this node entirely. |
timeoutMs | number | undefined | Max wait time in ms. |
continueOnFail | boolean | false | Workflow continues even if this node fails. |
dependsOn | string[] | undefined | Task IDs that must complete first. |
needs | Record<string, string> | undefined | Named deps. Keys become context keys, values are task IDs. |
label | string | human:<id> | Display label override. |
meta | Record<string, unknown> | undefined | Extra metadata. |
Schema-driven Example
How It Works
- The workflow reaches the
<HumanTask>node and enterswaiting-approvalstatus. - The human submits JSON input via
smithers approve(the input goes in thenotefield). - The compute function parses and validates the JSON against the
outputSchema. - If validation fails, the task retries — the human is prompted again (up to
maxAttempts). - On success, the validated data is written to the configured
output.
Submitting Input
Use the CLI to submit human input:Validation Retries
When the human provides invalid JSON (wrong shape, missing fields, wrong types), the task fails validation and retries. The retry policy uses zero delay (fixed backoff, 0ms) so the human can immediately re-attempt.
maxAttempts, the task fails.
Prompt fallback
When the task meta is read back from the database (for example by a UI or the CLI), the display prompt is resolved with a fallback chain:- The
promptprop value rendered to plain text at component creation time is stored inmeta.prompt. - At display time,
getHumanTaskPrompt(meta, fallback)returnsmeta.promptif it is a non-empty string, otherwise it returns the providedfallbackstring. - If
promptis a React element (e.g. an MDX component), it is rendered to markdown before storage viarenderPromptToText. The result is what humans see; no JSX or HTML tags reach the UI.
<HumanTask> always has a stable text representation of its prompt regardless of whether the original JSX tree is still in scope.
Request ID generation
Each<HumanTask> creates a human request record identified by a deterministic ID:
buildHumanRequestId(runId, nodeId, iteration) and is stable across retries within the same iteration. It is also used to link the human request record to the corresponding approval record: when a human submits input via smithers approve, the compute function looks up both records by this ID, prefers humanRequest.responseJson if present, and falls back to approval.note for backwards compatibility with approval-only submissions.
Durable deferred resolution
<HumanTask> uses the same durable deferred mechanism as <Approval>. When the node enters waiting-approval state, an @effect/workflow DurableDeferred is created and awaited by the executing task fiber. The deferred is keyed to the run, node, and iteration, so it survives process restarts: if the worker crashes while waiting, the next worker that picks up the task will re-await the same deferred and receive the resolution as soon as a human submits input.
When smithers approve is called, bridgeApprovalResolve resolves the deferred, which unblocks the awaiting fiber and lets the compute function proceed to read and validate the human input. No polling is needed.
Behavior
- Internally creates a
smithers:taskhost element withneedsApproval: trueand a compute function that reads human input from the database. - Same approval flow as
<Approval>— the node suspends and waits for human input. - With
async, later unrelated nodes in the same sequence may continue rendering and executing before the human submits input. - Schema validation happens at compute time, not at submission time.
- The
retriesprop is set tomaxAttempts - 1(first attempt + retries = total attempts).
<HumanTask> vs <Approval> vs needsApproval
| Use | When |
|---|---|
<HumanTask> | Human provides structured data matching a schema. Validation + retries. |
<Approval> | Human approves or denies. Decision persisted as ApprovalDecision. |
needsApproval on <Task> | Simple pause before an agent task. No separate value needed. |
Notes
- The human’s JSON input is stored in the approval
notefield as a JSON string. outputSchemais inferred fromoutputwhenoutputis a Zod schema.- Combine with
<Sequence>to gate downstream work on human input. - Use
ctx.outputMaybe(...)when rendering branches that consume an async human task’s result. - The
metafield includeshumanTask: true,maxAttempts, and thepromptfor UI rendering.