fabro/docs/human-tools/interviews.mdx
Bryan Helmkamp 35bd79a2ed
Update docs, frontend, marketing, and skills for .fabro extension
Update 47 MDX doc pages, OpenAPI spec, SVG diagram, language
grammar, frontend demo data, marketing page, skills, and README
to use .fabro extension. Add "fabro" to fileTypes in language
grammars. Document stack.child_workflow alongside stack.child_dotfile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 22:38:25 -04:00

115 lines
5.1 KiB
Text

---
title: "Interviews"
description: "How Fabro collects human input during workflow execution"
---
When a workflow reaches a [human gate](/workflows/human-in-the-loop), 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:
| Type | Description | CLI presentation |
|---|---|---|
| `YesNo` | Binary yes/no decision | `[Y/N]` prompt |
| `Confirmation` | Confirm an action (like YesNo) | `[Y/N]` prompt |
| `MultipleChoice` | Pick one option from a list | Arrow-key selector or numbered list |
| `MultiSelect` | Pick one or more from a list | Checkbox selector |
| `Freeform` | Open-ended text input | `>` prompt |
### Question structure
Each question carries metadata beyond the prompt text:
| Field | Description |
|---|---|
| `text` | The question displayed to the user |
| `question_type` | One of the types above |
| `options` | List of `{key, label}` pairs for choice questions |
| `allow_freeform` | Whether free-text input is accepted in addition to fixed options |
| `default` | Default answer used on timeout |
| `timeout_seconds` | How long to wait before using the default or timing out |
| `stage` | The node ID that generated this question |
| `metadata` | Arbitrary key-value metadata for integrations |
## Answer values
Answers are one of six variants:
| Value | Meaning |
|---|---|
| `Yes` | Affirmative response to a yes/no or confirmation question |
| `No` | Negative response |
| `Selected(key)` | A specific option was chosen (carries the option key) |
| `Text(string)` | Free-text input |
| `Skipped` | The user dismissed the question without answering |
| `Timeout` | The 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](/workflows/human-in-the-loop) 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](/workflows/transitions#human-gate-transitions).
## 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.
```bash
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` / `Confirmation` → `Yes`
- `MultipleChoice` / `MultiSelect` → first option
- `Freeform` → `"auto-approved"`
Enable it with the `--auto-approve` flag:
```bash
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.
```dot
approve [shape=hexagon, label="Approve?", human.default_choice="deploy"]
```