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.

8 sections

The App Agent layer includes two built-in interruption patterns:

  • requestApproval
  • requestInput

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:

  1. permission to continue
  2. 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:

TS
agent.approvals.pending

Resolve with:

TS
agent.approvals.resolve(id, true)  // approve
agent.approvals.resolve(id, false) // reject

Approval example#

TSX
{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:

TS
agent.requests.pending

Resolve with:

TS
agent.requests.submit(id, values)
agent.requests.cancel(id)

Each request contains:

  • title
  • prompt
  • fields
  • submitLabel
  • cancelLabel

Each field can be:

  • text
  • textarea
  • number
  • select
  • boolean

Example input request form#

TSX
{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:

TS
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 requestInput forms to the minimum number of fields needed
  • prefer requestInput for 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