Question types
Every human interaction is modeled as aQuestion with a type that determines how it’s presented:
Question structure
Each question carries metadata beyond the prompt text:Answer values
Answers are one of seven variants:
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:
- Each edge becomes an option, with the accelerator key parsed from the label (e.g.
[A] Approve→ keyA, label[A] Approve) - Edges with
freeform=trueare excluded from the option list and enable free-text fallback - The question text comes from the node’s
labelattribute - The question type defaults to
multiple_choicewhen fixed options exist andfreeformwhen only a freeform edge exists. Setquestion_type="yes_no",question_type="confirmation",question_type="multi_select", or another supported value to override it.
Channels
TheInterviewer 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) viadialoguer. When stdin is piped (non-TTY), it falls back to a line-based reader with numbered options.
Interrupted. That does not count as approval.
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 shows pending questions in the run page’s interview dock, listens for interview events, and posts answers back to the API.
The dock supports yes/no, confirmation, multiple choice, multi-select, and freeform questions. When multiple questions are pending, the dock lets you cycle through them without leaving the run detail page.
If the pending session disappears before an answer is submitted, the waiting question resolves as Interrupted.
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 callssubmit_answer() on the web interviewer, unblocking the workflow.
Slack interview prompts require Slack server credentials, an enabled [server.integrations.slack] table, and a destination channel. Most servers should configure server.integrations.slack.default_channel; lifecycle notifications use their own per-route channels instead.
Auto-approve
For fully automated runs or CI pipelines, the auto-approve interviewer answers every question without human input:YesNo/Confirmation→YesMultipleChoice/MultiSelect→ first optionFreeform→"auto-approved"
--auto-approve flag:
--auto-approve is intentionally distinct from an unanswered prompt. Auto-approve is an explicit operator choice to advance human gates automatically.
Timeouts
Questions can have atimeout_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
defaultanswer is set on the question, the default is used - If the timeout elapses with no default, the answer is
Timeout
human.default_choice attribute. If set, execution continues to the default target. Otherwise, the stage retries.
Interrupted and Skipped answers do not fall through to ordinary approval edges. To model an explicit unanswered path, add an edge such as condition="outcome=failed" or configure a retry_target.