Schema-Driven Elicitation#
Overview#
Schema-driven elicitation is the mechanism by which agents pause a run and request typed user input via a flat JSON schema rendered as an inline Ant Design form inside the playground agent chat. It is the first of three planned "interaction kinds" on the client-tool registry seam , replacing ad-hoc hand-built widgets with a payload-driven approach.
Two Use Cases#
- Standalone elicitation — Any agent can call
request_inputat any point during a run to collect non-secret structured input before proceeding (e.g., selecting a support platform, choosing a polling interval). - Agent onboarding clarification — The playground-native onboarding flow uses elicitation to let the setup agent issue clarifying
request_inputcalls mid-run while wiring a new agent from a free-text description .
Wire Contract#
The tool is named request_input and is a reserved static-catalog client tool auto-embedded in every build kit — no SDK changes needed to use it .
Its input schema :
message(string, required) — one-to-two sentence context shown above the formrequestedSchema(object, required) — a flat JSON Schema: top-level primitive properties only (string,number,integer,boolean). Supportsenum,title,default, andformathints.- No nested objects or arrays are allowed.
- Never request secrets — use
request_connectionfor credentials.
Supported format values:
date,date-time→DatePicker(withshowTimefordate-time)email,uri→Inputwith validationmultiline→Input.TextArea(3 rows)
Format aliases that LLMs naturally emit are normalized: textarea/multi-line/long-text → multiline; datetime → date-time; url → uri .
Response envelope:
accept→{action: "accept", content: {...}, humanFriendlyMessage}decline→{action: "decline", humanFriendlyMessage}cancel→{action: "cancel"}
Dispatch Mechanism (render.kind)#
Because AI SDK v6 tool chunks are strict and drop inline fields, the render.kind hint cannot be embedded directly in the tool call. It travels as a sibling data-render stream part, merged into a per-message map by buildRenderMap .
Two-stage dispatch in the client-tool registry :
- Primary: match on
render.kind = "elicitation"from the render map. - Fallback: match on
toolName. - Default: if neither matches, auto-settle with
"not handled by this client"to prevent hang.
The ElicitationWidget component handles rendering, validation, and settlement . The resume predicate in agentApprovalResume.ts also keys on render.kind to know when to auto-resume after settlement.
Cross-Turn Isolation#
The runner's client-tool output store is scoped to the current turn only (at or after the latest user message). This prevents a second identical request_input call in a later turn from being silently fulfilled by the first turn's answer — it pauses and shows a fresh form . Approvals remain full-history (grants are idempotent).
Part States and Replay#
Elicitation parts cycle through four replayable states (defined in elicitation.ts):
| State | Rendered as |
|---|---|
pending | Live interactive form |
submitted | Read-only chip showing accepted values |
declined | Read-only chip |
cancelled | Read-only chip |
degraded | Unsupported-payload error surface |
Reload while pending → live form re-renders and still accepts. Reload after settlement → chip replays read-only .
Contract Pinning#
All contract logic lives in @agenta/shared: payload validator, action envelopes, part-state derivation, format mapping, and a secret-field denylist. Both vitest and pytest assert against shared golden fixtures at web/packages/agenta-shared/tests/fixtures/elicitation_*.json, preventing wire-dialect drift across languages .
Agent Onboarding Integration#
The playground-native onboarding (PR #5076) drives elicitation through the same request_input client tool. The useAgentOnboarding hook mints an ephemeral agent, commits it in-place via history.replaceState, and the setup agent can issue request_input calls mid-run to collect clarifying details (e.g., which support platform to watch, scheduling preferences) before finalizing the agent configuration .
Key Source Files#
| File | Purpose |
|---|---|
ElicitationWidget.tsx | Form rendering, validation, settlement, chip replay |
elicitation.ts | Shared contract: validator, envelopes, part-state derivation, format normalization |
renderMap.ts | Builds per-message render.kind map from data-render stream parts |
SchemaForm.tsx | Format-aware field renderer (opt-in via formats={true}) |
static_catalog.py | Defines the request_input tool in the static workflow catalog |
responder.ts | Turn-scoped client-tool output store (cross-turn isolation fix) |
agentApprovalResume.ts | Resume predicate keyed on render.kind |
decisions.md | Locked design decisions for interaction kinds |
Design Decisions#
- Closed component set — three interaction kinds (
elicitation,display,config-card); no open-ended generative UI. Model-composed arbitrary UI is explicitly out of scope . - Secrets never on the chat wire — platform widgets post to the vault API directly; use
request_connectionfor credentials . render.kindis required — without it, a client tool settles but never auto-resumes .- Adding a new kind costs one registry entry plus a contract module — not a new frontend widget per tool .