Human-Gate Steps
Use a human_gate when the workflow needs an authorized operator to approve or reject a decision, or answer questions before work continues. The run inspector presents the authored prompt and enables input only for an authorized operator.
Configuration
- key: approve_release
name: Approve Release
kind: human_gate
needs: [prepare_release]
humanGate:
prompt: "Approve ${steps.prepare_release.outputs.releaseName}?"
approvalText: Ship
rejectionText: Hold
waitsFor:
kind: workflow_input
expected: [approved, rejected]
authorizedHandles: ["@release-operator"]
outputs:
outcome: "$.normalized.input.outcome"| Field | Required | Purpose |
|---|---|---|
humanGate.enabled | No | Set false only when this version intentionally bypasses the gate |
humanGate.prompt | No | Decision shown to the operator |
humanGate.approvalText | No | Approval button label |
humanGate.rejectionText | No | Rejection button label |
humanGate.questions | Questions mode | Literal question array or one exact reference to an array |
humanGate.references | No | Up to eight labeled task, session, run, or URL links |
humanGate.preview | No | One to five supporting documents for an approval or Questions gate |
humanGate.feedback | No | Writer session, task, and review that receive requested corrections; requires at least one preview document |
humanGate.pullRequestProposal | No | Editable pull-request proposal backed by frozen branch evidence |
waitsFor.expected | Yes | Approval gates accept one or both of approved, rejected; Questions gates require answered and may include rejected |
waitsFor.authorizedHandles | Yes | Operators allowed to decide |
outputs | No | Named outcomes or input values for later steps |
When copy is omitted, approval gates use the step name, Approve, and Reject. Questions gates use the step name, Submit answers, and Reject.
Ask questions
Select a Human Gate step in the Builder, open Approval content in its inspector, and choose Questions to collect structured answers while the run waits. Questions require an enabled gate. Enter a literal list or an exact reference to an array produced by an earlier step; either way, the resolved value must follow the same question format. For generated questions, the reference must resolve to an array, not JSON text:
humanGate:
questions: "${steps.ask.outputs.questions}"Add 1–12 questions with unique IDs; each can be text, single_choice, multiple_choice, or yes_no. Use descriptive question and option IDs, such as change_type and bug_fix. IDs start with a letter and may contain letters, numbers, _, or - (up to 64 characters). If the Builder flags an ID, choose a different descriptive name. Question labels must be nonblank and at most 1,000 characters. Choice questions need 2–20 options with unique IDs and nonblank labels of up to 300 characters. Questions are required by default; set required: false to let the operator leave one unanswered. Questions can include document previews for context. Use separate gates for feedback, pull-request proposals, and session-output evidence.
- key: clarify_change
name: Clarify Change
kind: human_gate
humanGate:
prompt: "Give the next agent the context needed to continue."
questions:
- id: change_type
type: single_choice
label: "What kind of change is this?"
options:
- { id: bug_fix, label: Bug fix }
- { id: feature, label: Feature }
- id: extra_context
type: text
label: "What should the next agent know?"
required: false
waitsFor:
kind: workflow_input
expected: [answered, rejected]
authorizedHandles: ["@release-operator"]
onFailure:
action: block
outputs:
outcome: "$.normalized.input.outcome"
questions: "$.normalized.input.questions"
answers: "$.normalized.input.payload.answers"Add document context
When an agent’s specification, brief, or decision file helps explain the questions, add it through the Builder’s Preview documents controls in the same Approval content inspector. A Questions gate can show one to five documents alongside its questions:
humanGate:
questions: "${steps.write_spec.outputs.questions}"
preview:
documents:
- key: spec
label: Specification
path: "${steps.write_spec.outputs.specPath}"
sourceSessionUlid: "${steps.write_spec.outputs.sessionUlid}"Add write_spec to the gate’s needs and declare its questions, specPath, and sessionUlid outputs. The document path is read from the session that produced it; use that session’s output for sourceSessionUlid.
The operator selects a Context document above the questions and can choose Reload context to read its current contents. Switching documents, reloading, or encountering a preview error keeps drafted answers. If a preview cannot load, its error is shown with a reload control; the operator can still answer the questions. Submitting answers records clarification, not approval of the document.
Use the answers
The operator can answer each question in its declared format or choose Write my own answer for a written response. A written answer is valid for any question, including a choice or yes/no question. Required questions need an answer; text answers must be nonblank and are limited to 10,000 characters. Optional questions may be omitted. After an answered submission, the questions shown to the operator and their answers are available as step outputs, with answers keyed by question ID, as shown above. A text response can answer a choice question; for example, the change_type response can be:
{
"answers": {
"change_type": { "type": "text", "value": "A regression affects the export path." }
}
}Map the question list and answers into a later agent’s prompt so it can use both the original questions and responses:
- key: continue_change
name: Continue Change
kind: session
needs: [clarify_change]
session:
action: launch_prompt
prompt: |
Continue the change using the operator's answers.
Questions: ${steps.clarify_change.outputs.questions}
Answers: ${steps.clarify_change.outputs.answers}
Finish with a JSON object whose outcome is change_completed.
launcherConfigKey: coder-codex
role: executor
waitsFor:
kind: workflow_input
expected: [change_completed]
gates:
- key: completion
kind: session_output
source:
event: final_agent_message
extract: { format: json, path: $.outcome }
outcomes: { change_completed: change_completed }
onInvalid: { action: block }
outputs:
sessionUlid: "$.normalized.session.sessionUlid"
outcome: "$.normalized.input.outcome"The gate requires an answered outcome and may also offer rejected; it does not accept approved. Rejection does not stop the run by itself: route it to the intended follow-up branch, or set onFailure.action: block when the run should stop at the gate. Submitting answers records input; it does not approve a diff or publish work.
The question list is set when the gate opens and stays the same while it waits for answers. If authored or generated question data is missing or invalid, the run blocks with a visible recovery message. A running workflow keeps its selected version; correct invalid questions in a new workflow version and start a new run. Draft answers stay in the current browser, survive refreshes, closing the dialog, and submission failures, and do not sync to another device. If Valdr reports that input was recorded but the workflow could not advance, check the refreshed run status before acting again; do not submit the same answers again. If the run is still waiting after an uncertain result, refresh its status and submit the retained draft again.
humanGate.enabled: false intentionally bypasses the decision and records the step as approved without waiting for human input. The step must still include approved in waitsFor.expected. Use a new workflow version when changing this setting. A run already waiting at an enabled gate still requires its operator decision.Link supporting records
References give the operator direct context without changing the decision contract:
humanGate:
references:
- kind: task
target: "${workflow.inputs.taskKey}"
label: Open task
- kind: run
target: "${runtime.runUlid}"
label: Open runEach reference needs kind, target, and label. Supported kinds are task, session, run, and url.
Preview reviewed documents
A gate can present one to five documents before the operator decides:
humanGate:
prompt: "Approve the reviewed specification?"
approvalText: Approve spec
rejectionText: Reject
preview:
documents:
- key: spec
label: Specification
path: "${steps.create_spec.outputs.path}"
sourceSessionUlid: "${steps.create_spec.outputs.writerSessionUlid}"Each document needs a unique key, a visible label, its path, and the session that created it.
Send feedback before deciding
humanGate:
feedback:
sourceSessionUlid: "${steps.create_spec.outputs.writerSessionUlid}"
taskKey: "${workflow.inputs.taskKey}"
reviewId: "${steps.create_spec.outputs.reviewId}"Feedback returns a correction request to the configured writer session and refreshes the preview. It does not approve or reject the gate. The operator reviews the updated document, then makes the final decision.
The Builder exposes structured Preview documents and Feedback controls. Use its YAML dialog when you need to inspect or edit the full definition directly.
Approve a pull-request proposal
A pull-request gate lets the operator edit the title, body, and draft state before approving publication. Bind it to frozen repository evidence:
humanGate:
prompt: Review and approve the pull request proposal.
approvalText: Publish pull request
rejectionText: Reject
pullRequestProposal:
repository: "${steps.observe_remote.outputs.repositoryOwner}/${steps.observe_remote.outputs.repositoryName}"
remoteName: "${workflow.inputs.remoteName}"
baseBranch: "${workflow.inputs.baseBranch}"
headBranch: "${workflow.inputs.headBranch}"
headSha: "${steps.resolve_head.outputs.resolvedSha}"
writerSessionUlid: "${steps.draft_proposal.outputs.sessionUlid}"
evidenceDigest: "${steps.read_diff.outputs.digest}"
evidence:
runUlid: "${steps.read_diff.outputs.runUlid}"
stepRunUlid: "${steps.read_diff.outputs.stepRunUlid}"
initial:
title: "${steps.draft_proposal.outputs.title}"
body: "${steps.draft_proposal.outputs.body}"
draftFrom: "${workflow.inputs.initialDraft}"evidence.runUlid and evidence.stepRunUlid are required and identify the run and step that captured the frozen evidence. Bind them to the same evidence-producing step as evidenceDigest.
draftFrom must be an exact reference; the referenced value must resolve to a Boolean at runtime. You may use a literal Boolean in initial.draft instead. Initial and edited titles must be one line and at most 256 characters; bodies must use LF line endings and be at most 65,536 bytes. Map $.normalized.input.payload.title, .body, and .draft as step outputs when the later GitHub step should publish the operator-approved values.
Route the decision
- key: release
name: Release
kind: condition
needs: [approve_release]
when:
- kind: value_equals
value: "${steps.approve_release.outputs.outcome}"
equals: approved
outputs:
outcome: "${steps.approve_release.outputs.outcome}"This example publishes the decision only on the approved branch; replace the condition with your intended follow-up action when composing the workflow. A rejection is recorded as the rejected workflow value. Without a block or fail policy, route it to the appropriate follow-up step. Set onFailure.action to block or fail on the human gate when rejection should stop the run there.
Next step
Continue with Outcome route to route the decision explicitly.