Tide

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.ts

A 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 become decision.payload and decision.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.

On this page