Skip to main content
When a workflow reaches a human gate, it needs to pause and wait for a person to respond. The interviewer is the abstraction that makes this work — it presents a question, collects an answer, and returns it to the engine so execution can continue. Fabro ships with several interviewer implementations for different environments: an interactive terminal prompt for the CLI, a web-based queue for the API server and web UI, an auto-approve mode for CI, and record/replay support for testing.

Question types

Every human interaction is modeled as a Question with a type that determines how it’s presented:
TypeDescriptionCLI presentation
YesNoBinary yes/no decision[Y/N] prompt
ConfirmationConfirm an action (like YesNo)[Y/N] prompt
MultipleChoicePick one option from a listArrow-key selector or numbered list
MultiSelectPick one or more from a listCheckbox selector
FreeformOpen-ended text input> prompt

Question structure

Each question carries metadata beyond the prompt text:
FieldDescription
textThe question displayed to the user
question_typeOne of the types above
optionsList of {key, label} pairs for choice questions
allow_freeformWhether free-text input is accepted in addition to fixed options
defaultDefault answer used on timeout
timeout_secondsHow long to wait before using the default or timing out
stageThe node ID that generated this question
metadataArbitrary key-value metadata for integrations

Answer values

Answers are one of six variants:
ValueMeaning
YesAffirmative response to a yes/no or confirmation question
NoNegative response
Selected(key)A specific option was chosen (carries the option key)
Text(string)Free-text input
SkippedThe user dismissed the question without answering
TimeoutThe question’s timeout elapsed without a response
An answer can also carry a selected_option (the full {key, label} pair) and a text field for freeform input.

How human gates build questions

When the engine reaches a human gate node (shape=hexagon), the human handler builds a question from the node’s outgoing edges:
  1. Each edge becomes an option, with the accelerator key parsed from the label (e.g. [A] Approve → key A, label [A] Approve)
  2. Edges with freeform=true are excluded from the option list and enable free-text fallback
  3. The question text comes from the node’s label attribute
The handler then passes the question to the interviewer, waits for an answer, and maps it back to an edge for transition.

Channels

The Interviewer trait has a simple interface — ask(question) → answer — and Fabro provides implementations for each delivery channel:

Console

The default for CLI runs. On a TTY, the console interviewer uses interactive widgets (arrow-key selection, checkbox multi-select, confirm prompts) via dialoguer. When stdin is piped (non-TTY), it falls back to a line-based reader with numbered options.
fabro run workflow.fabro
# At a human gate:
# ? Approve Plan
#   [1] A - [A] Approve
#   [2] R - [R] Revise
# Select:

Web

The default for API server runs. The web interviewer holds questions in a queue until answers are submitted externally — typically by the web UI or a REST API call. Each question gets a unique ID (e.g. q-1), and the ask() call blocks on a oneshot channel until submit_answer(id, answer) is called. This decoupling means the workflow engine and the user interface can run in different processes. The web UI polls for pending questions and posts answers back to the API.

Slack

Fabro’s Slack integration uses the web interviewer under the hood. When a human gate fires, the pending question is rendered as a Slack message with interactive buttons. When a user clicks a button, the Slack event handler calls submit_answer() on the web interviewer, unblocking the workflow.

Auto-approve

For fully automated runs or CI pipelines, the auto-approve interviewer answers every question without human input:
  • YesNo / ConfirmationYes
  • MultipleChoice / MultiSelect → first option
  • Freeform"auto-approved"
Enable it with the --auto-approve flag:
fabro run workflow.fabro --auto-approve

Timeouts

Questions can have a timeout_seconds field. When set, Fabro wraps the interviewer call with a timeout:
  • If the user answers before the deadline, their answer is used normally
  • If the timeout elapses and a default answer is set on the question, the default is used
  • If the timeout elapses with no default, the answer is Timeout
The human handler then checks the node’s human.default_choice attribute. If set, execution continues to the default target. Otherwise, the stage retries.
approve [shape=hexagon, label="Approve?", human.default_choice="deploy"]