Human-in-the-loop
Suspend a workflow for a human decision, then resume it.
A workflow can pause for a human decision with hitl.wait(...). The run
suspends, returns a WaitingHuman status, and is durably parked until a person
resolves the task with tide resume. On resume the workflow replays its journal
to the suspension point and continues with the decision in hand — no work before
the wait is repeated.
Waiting for a decision
hitl.wait(request) describes the task a human must act on and returns their
decision:
const schema = S.object({ documentId: S.text() });
export default {
schema,
main: async (ctx: Infer<typeof schema>) => {
const decision = await hitl.wait({
title: `Review extracted evidence for ${ctx.documentId}`,
description: "Approve or deny the extracted evidence.",
tags: ["evidence-review"],
priority: "high",
moduleName: "evidence_review",
payload: { summary: "", review_notes: "" },
classification: "evidence",
});
log("human action:", decision.action);
log("payload:", JSON.stringify(decision.payload));
await writeFile("decision.json", JSON.stringify(decision, null, 2));
},
};The request shape (title is required; description, tags, priority,
payload, classification, moduleName, … are optional) is the
HitlWaitRequest type. The returned HumanWaitOutcome carries action,
payload, comment, and provenance (resolvedBy, resolvedAt).
With defineWorkflow, the same surface is on the context as ctx.hitl.
Running and suspending
Run the workflow as usual:
tide run review.ts -i review-001 --input '{"documentId":"doc-42"}'When it hits hitl.wait, the run suspends. Tide prints the pending task and the
exact resume command to stderr:
suspended: waiting for human task task-… (wait #0)
resume with: tide resume review-001 --action <action> review.tsA suspended run is not a failure — its exit code is 0, and its journal +
fork are kept intact so the resume can reattach.
Resuming
Record the human's decision and continue:
tide resume review-001 review.ts --action approved \
--payload '{"summary":"looks correct","review_notes":"ok"}' \
--comment "verified against source"<invocation-id>and the workflow file are positional.--action <action>is required (e.g.approved/rejected— any string your workflow branches on).--payload <json>and--comment <text>are optional; they becomedecision.payloadanddecision.comment.
tide resume appends the resolution to the journal, replays the run up to the
hitl.wait, and resumes from there with the outcome. Multiple waits in one
workflow resolve in order — each tide resume resolves the lowest outstanding
wait.
Default posture and capability wiring
The hitl capability is provided by the host. The shipped tide binary wires a
local HITL service so hitl.wait suspends for tide resume as shown above. In
the argon-free runtime with capabilities disabled, hitl.wait refuses
deterministically rather than suspending.
Driving it programmatically
tide run --json makes the suspend/resume loop machine-readable: a suspended
run emits "status": "waitingHuman" with a pendingHumanWait object (the task
id and wait ordinal) and the invocation id, so an agent can issue the matching
tide resume. See Driving Tide from an agent.
Related
- tide serve — resolve waits over HTTP with
POST /v1/invocations/{id}/resolve. - CLI reference —
runandresumeflags. - Workflow API — the
hitlsurface.