Agent SDK
Handle approvals and input requests
Render the App Agent's built-in approval and structured-input interruption model so the agent can pause, ask, and continue cleanly.
The App Agent layer includes two built-in interruption patterns:
requestApprovalrequestInput
These are exposed to the agent as built-in client tools. You do not register them yourself.
Your job in the app is to render the pending state and resolve it.
Why this exists#
Real product agents often need to pause mid-turn because they are missing one of two things:
- permission to continue
- specific structured details
That should not be modeled as a vague assistant message plus a local UI hack.
The App Agent controller standardizes both flows.
Approvals#
The agent uses requestApproval when it needs explicit user confirmation before continuing.
Read from:
agent.approvals.pendingResolve with:
agent.approvals.resolve(id, true) // approve
agent.approvals.resolve(id, false) // rejectApproval example#
{agent.approvals.pending.map((approval) => (
<div key={approval.id}>
<h3>{approval.title}</h3>
{approval.rationale ? <p>{approval.rationale}</p> : null}
<ol>
{approval.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ol>
<button onClick={() => agent.approvals.resolve(approval.id, true)}>
{approval.confirmLabel ?? "Approve"}
</button>
<button onClick={() => agent.approvals.resolve(approval.id, false)}>
{approval.cancelLabel ?? "Cancel"}
</button>
</div>
))}Structured input requests#
The agent uses requestInput when it needs labeled, structured follow-up data instead of a free-form answer.
Read from:
agent.requests.pendingResolve with:
agent.requests.submit(id, values)
agent.requests.cancel(id)Each request contains:
titlepromptfieldssubmitLabelcancelLabel
Each field can be:
texttextareanumberselectboolean
Example input request form#
{agent.requests.pending.map((request) => (
<form
key={request.id}
onSubmit={(event) => {
event.preventDefault();
const values = {
dueDate: formState.dueDate,
priority: formState.priority,
assignToCurrentUser: formState.assignToCurrentUser,
};
agent.requests.submit(request.id, values);
}}
>
<h3>{request.title}</h3>
{request.prompt ? <p>{request.prompt}</p> : null}
{request.fields.map((field) => {
if (field.kind === "boolean") {
return (
<label key={field.key}>
<input
type="checkbox"
checked={Boolean(formState[field.key])}
onChange={(event) =>
setFormState((current) => ({
...current,
[field.key]: event.target.checked,
}))
}
/>
{field.label}
</label>
);
}
return (
<label key={field.key}>
{field.label}
<input
type={field.kind === "number" ? "number" : "text"}
value={String(formState[field.key] ?? "")}
onChange={(event) =>
setFormState((current) => ({
...current,
[field.key]: event.target.value,
}))
}
/>
</label>
);
})}
<button type="submit">{request.submitLabel ?? "Continue"}</button>
<button type="button" onClick={() => agent.requests.cancel(request.id)}>
{request.cancelLabel ?? "Cancel"}
</button>
</form>
))}When the agent should use each#
Use approval when the user needs to explicitly authorize the plan.
Use requestInput when the agent is blocked on structured missing details such as:
- count
- date
- priority
- scope choice
- boolean decision
- labeled option selection
Teach the agent when to interrupt#
Use your system prompt or appContext to tell the agent when it should pause.
Example:
appContext: {
hostPolicy:
"Before any multi-step or destructive server-side mutation, call requestApproval. If you need labeled missing details such as a date, count, assignee, or scope choice, call requestInput instead of asking a free-form follow-up.",
}Best practices#
- keep approval titles short
- keep approval steps ordered and concrete
- keep
requestInputforms to the minimum number of fields needed - prefer
requestInputfor dates, numbers, booleans, or explicit options - prefer a normal conversational answer when free-form clarification is enough
- render these interruptions in your product UI, not as plain assistant text
