Human-in-the-loop
Approval gates and questions are language primitives — they return values you branch on.
-- Doc example: human interaction. The primitives are interactive; this shows the
-- documented NON-TTY behavior (CI/tests/pipes), which is deterministic.
intent: "doc example: human interaction (non-TTY)"
print("ask picked → " + (ask "Pick an environment" with ["staging", "prod"])) -- no TTY → first
test "ask with options takes the FIRST option when there is no TTY (CI/tests/pipes)"
let choice be ask "Pick an environment" with ["staging", "prod"]
assert_eq(choice, "staging")
Primitives
let ok be approve "Deploy to production?" -- yes/no gate (returns a bool)
confirm "Send email to 500 customers?" -- confirmation
let env be ask "Which environment?" with ["staging", "prod"]
show data as "Preview" -- display to a human
Use them as expressions:
when approve "Large payment: $" + text(amount)
process_payment()
otherwise
cancel()
Backends
- Terminal — interactive stdin/stdout.
- Auto — auto-approves everything (CI/testing).
- Queue — async; the agent blocks while a human answers later (web UI).
- Callback — programmatic, for embedding.
No TTY (pipes / CI / tests)
Without an interactive TTY, free-text ask "q" returns "" and ask "q" with [opts] takes the first option (as the doctest above shows). Don't rely on free-text ask for input there. For stdin that works with pipes, use read_line(prompt?); for config-style input that's trivial to test, use env("NAME", "default").