Gates & approvals

A gate is a checkpoint step (kind: gate) inside a workflow phase. When the pipeline reaches one, the gate's subtask goes blocked and nothing downstream of it runs until the gate resolves. Gates are how a multi-hour autonomous pipeline stays reviewable: approve the storyboard before the scenes render, approve the deploy before it ships.

Gate kinds

The spec supports four gate_kind values; human gates are the fully shipped surface (portal buttons, Discord ping, REST endpoint):

Kind Resolves when
human A person approves or rejects (portal or REST).
webhook An external system calls back (resolved via the runtime's webhook-gate tooling).
schedule A scheduled time arrives.
pipeline_dep Another pipeline reaches a required state.

The approval flow

  1. The runner reaches the gate; the subtask flips to blocked and the project stops advancing along that path.
  2. The project detail page raises an amber awaiting-approval banner naming the gate and its phase, with a Review & decide link to the gate's subtask page.
  3. If Discord notifications are configured, a ping goes out carrying a deep link to that portal page — the review itself happens in the portal, not in Discord.
  4. On the subtask page you get Approve and Reject buttons, each with an optional comment. If the gate reviews a producer step, the page shows that step's output right there (a gate with no explicit reviews falls back to showing its first consumed sibling — that's how an edit-session's plan gate shows you the plan).
  5. Once resolved, the project wakes and autodrive finishes the rest — no manual nudge needed.

Decision semantics

Only two decisions exist: approved and rejected. What a rejection does depends on the gate's position:

  • Approve — the gate succeeds (recording a human_decision.v1 output) and the pipeline continues.
  • Reject a reviewing gate (the gate reviews a producer step) — the verdict was successfully collected, so the gate succeeds carrying the rejection; the review chain converts it into one producer rework (attempt_reason: review_failure) with your comment threaded into the retry brief, and the chain — including the gate — resets to pending so you re-decide on the rework.
  • Reject a standalone gate (no reviews) — the gate fails with the decision recorded, the task fails, and the project pauses for a human. Rejection never abandons a project.

Resolving via REST

POST /v1/projects/{project_id}/subtasks/{subtask_id}/resolve-gate
{ "decision": "approved", "comment": "Storyboard looks right." }

decided_by is always derived from your verified session (the portal path sends portal:<huudis-user-id>); it cannot be spoofed from the body. Response:

{
  "subtask_id": "…",
  "step_slug": "approve_storyboard",
  "decision": "approved",
  "decided_by": "portal:usr_…",
  "new_status": "succeeded",
  "reviewed_producer": "storyboard",
  "review_chain_resolved": true
}

Errors: 400 invalid decision (anything other than approved/rejected), 404 subtask not in the project, 409 when the subtask isn't a blocked human gate — not a gate, wrong gate kind, not blocked, or you lost a resolution race.

Not exposed in the SDKs yet — call the REST endpoint directly.

Edit-session plan gates

Edit sessions use a dedicated approve_plan gate with a wider decision set (approved, rejected, request_changes, edit_plan + a revised plan) and its own endpoint (POST …/edit-sessions/{task_id}/approve).

Next