fabro/docs/execution/interviews.mdx
Bryan Helmkamp d002aa0b51 Rename arc run startarc run (#5)
* arc(01KK7524KNGTPS4090QMF87FJN): implement (success)

Arc-Run: 01KK7524KNGTPS4090QMF87FJN
Arc-Completed: 2
Arc-Checkpoint: 1ff03c704805dfe8e7c37b37f97bd06dfa0e5dc5

* Fix: restore trailing newlines stripped by previous commit

* arc(01KK7524KNGTPS4090QMF87FJN): simplify (success)

Arc-Run: 01KK7524KNGTPS4090QMF87FJN
Arc-Completed: 3
Arc-Checkpoint: 21771adfd26283a1d1e6b8a123a83a0c4277db48

---------

Co-authored-by: arc <arc@local>
Co-authored-by: Arc Assistant <assistant@arc.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:14:50 -04:00

115 lines
5.1 KiB
Text

---
title: "Interviews"
description: "How Arc 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.
Arc 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 Arc 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
arc run workflow.dot
# 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
Arc'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
arc run workflow.dot --auto-approve
```
## Timeouts
Questions can have a `timeout_seconds` field. When set, Arc 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"]
```