Selection Flow
People speak imprecisely. “Build a presentation for Guggenheim” — but which Guggenheim? The museum in New York, the one in Bilbao, Guggenheim Partners, the foundation? When an agent can’t safely guess, it should show the candidates and let a human pick — then continue with the chosen one.
That’s a different shape from approval. Approve/Reject is a yes/no on a fixed
payload; disambiguation is pick one of N. elicit_selection is the tool for it.
The idea
Section titled “The idea”Write the selection skill once; the server adapts. Your skill calls
elicit_selection, then reuses the same
elicit_await →
elicit_result loop as the
Approval Flow. Underneath, the server renders the
options as a review page and (best-effort) pushes a URL-mode elicitation prompt
to hosts that advertise support; every host reaches the same outcome through the
same long-poll. The reviewer’s choice comes back as selectedIds.
One or many
Section titled “One or many”elicit_selection takes a list of options (each with a unique id) and two
optional bounds:
- Single-select (the default,
minSelections/maxSelectionsboth1) — the disambiguation case. The review page renders radio buttons; the reviewer picks exactly one. - Multi-select — widen the range (e.g.
maxSelections: 3) and the page renders checkboxes. Use it for “pick the services to deploy” or “choose up to three reviewers.”
Either way the result is the same shape: elicit_result returns
selectedIds — a string array of the chosen ids (one element in the
single-select case). The server validates every submitted id against the option
set and enforces the min/max bounds, so a malformed submission never resolves the
request.
Rendering the list
Section titled “Rendering the list”By default the page shows a plain id + label table — zero configuration. When
you want the richer presentation the disambiguation case deserves (logos,
domains, a change table), supply optional LiquidJS
templates — rowTemplate, headingTemplate, footerTemplate, hoverTemplate —
plus stock css. Each option’s data is available to rowTemplate as
{{ option.* }}. See the elicit_selection reference
for the full field list and examples.
The rendering is safe by construction, and it’s why selection is trustworthy:
- The table renders inside a null-origin sandboxed iframe with no scripting — template markup is inert, and it can’t read the reviewer’s session.
- The radio/checkbox controls are native, injected by the server — a template can’t spoof or intercept which option the human actually picks.
- Interpolated option data is HTML-escaped, and the submission is authorized by a signed, per-render token (not cookies) that the sandbox can’t forge.
The tools
Section titled “The tools”Selection reuses the approval polling tools — only the first call is new. Each has its own reference page with exact input/output shapes:
elicit_selection— record the options and (best-effort) push a URL-mode prompt; always returnsreviewUrl.elicit_await— bounded long-poll until the reviewer decides or the wait bound elapses.elicit_status— non-blocking status read.elicit_result— fetch the outcome; for a completed selection it returnsselectedIds(idempotent, every fetch recorded).
How a request plays out
Section titled “How a request plays out”The skill’s column never changes from the Approval Flow — the only new step is the
first call. Both flows converge on the same elicit_await wake.
Choosing, or refining
Section titled “Choosing, or refining”The review page also carries a Reject — refine search control. Selection isn’t
always “one of these is right”: sometimes the list is wrong and the human wants to
narrow the query. Rejecting resolves the request as rejected (no selectedIds),
which the skill reads as “none of these — gather a better candidate set and ask
again.” It’s the selection analog of a rejected approval.
The long-poll loop
Section titled “The long-poll loop”elicit_await behaves exactly as it does for approvals — bounded so it always
returns before the host’s tool-call timeout:
- A decision landed — the reviewer picked or refined; the call wakes immediately.
- The bound elapsed — it returns
"pending"; callelicit_awaitagain to keep waiting.
On a URL-capable host the reviewer likely acts on the pushed prompt; on any other
host they open reviewUrl. Either way the same wake resolves the same loop.
Expiry
Section titled “Expiry”Every request carries a TTL (timeoutSeconds, default 1 hour, clamped 60 seconds to
24 hours). Once it passes, the request is expired — a terminal state.
elicit_await and elicit_status both report "expired" at the deadline, and
the review page refuses a late selection. Ask the agent to resend if that happens.