recipe / 02

Specimen

Preview the live recipe, then copy HTML or Install. CSS is composition-only: copy the listed component stylesheets.

Preview

live-run / release notestool waiting / host decision needed
write-filewaiting
{"actionId":"write-release-notes-01","path":"release-notes.md","content":"# Release notes\n\n- Added agent-ui recipes"}

The file proposal is ready; the host has not written anything yet.

approval required

Write release-notes.md?

⌘↵ Approvewaiting

write-file · release-notes.md · actionId=write-release-notes-01 · exact action shown

Why this is safe

Draft only touches a new markdown file outside production paths and secret stores.

Code

tool-approval.html
<section class="au-tool-call" data-au-state="waiting" aria-label="Release notes tool call">
  <header class="au-tool-call__header"><span class="au-tool-call__name">write-file</span><span class="au-tool-call__status">waiting</span></header>
  <pre class="au-tool-call__args">{"actionId":"write-release-notes-01","path":"release-notes.md"}</pre>
  <p class="au-tool-call__output">The file proposal is ready; the host has not written anything yet.</p>
</section>
<section class="au-approval-gate" data-au-state="waiting" data-action-id="write-release-notes-01" aria-labelledby="approval-title">
  <header class="au-approval-gate__header">
    <div>
      <p class="au-approval-gate__eyebrow">approval required</p>
      <h2 class="au-approval-gate__title" id="approval-title">Write release-notes.md?</h2>
    </div>
  </header>
  <p class="au-approval-gate__action">write-file · release-notes.md · exact action shown</p>
  <form class="au-approval-gate__footer" method="post">
    <button class="au-btn au-btn--ghost" type="submit" name="decision" value="reject">Keep editing</button>
    <button class="au-btn au-btn--primary" type="submit" name="decision" value="approve">Approve write</button>
  </form>
</section>
Composition only — copy component CSS from tool-call, approval-gate, and button. Leave recipes.css in docs; product hosts copy component CSS only.

Install

head.html
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="themes.css">
<link rel="stylesheet" href="components/button/button.css">
<link rel="stylesheet" href="components/tool-call/tool-call.css">
<link rel="stylesheet" href="components/approval-gate/approval-gate.css">
<link rel="stylesheet" href="host-chrome.css">

Static state matrix

untrusted text stays text

Arguments and output are rendered as text. The component does not parse markup, execute commands, or imply that a waiting call succeeded.

running output pending

readrunning
{"path":"CHANGELOG.md"}

Waiting for the host response.

success output received

readsuccess · 0.4s
{"path":"CHANGELOG.md","offset":0,"limit":30}

Found 3 headings in the selected range.

error host rejected output

write-fileerror
{"path":"release-notes.md"}

Permission denied; write aborted by the host.

cancelled host stopped call

write-filecancelled
{"actionId":"write-release-notes-01"}

The host cancelled this call before execution.

Contract and safety

Install
tokens · tool-call · approval-gate · button
States
tool: running…cancelled · gate: waiting…cancelled
Host
Validate, authorize, execute, audit
For agents
tool-approval.contract
# agent-ui / recipe / tool-approval
recipe: tool-approval
purpose: Show exact tool args/output and require a human decision before host-owned execution.

install:
  - tokens.css
  - tool-call.css
  - approval-gate.css
  - button.css (actions)

tool_call_states: running | waiting | success | error | cancelled
approval_gate_states: waiting | success | error | cancelled
no_javascript: Exact arguments, output, action ID, and native decision form remain visible. Host receives the normal POST.
accessibility: Label both regions; expose status as text; use concrete button labels; status dots are decorative only.
security: Escape arguments and output. Bind approval to an immutable action ID plus exact serialized tool name and arguments.
host_owns: validation, authorization, execution, audit, and invalidation of stale approvals.

rules:
  - Approved UI still needs host validation before anything runs.
  - Prefer an optional __rationale that explains why the action is safe.
  - For a policy choice before acting, stack approval-gate with ask-user-question.

Compose a judgement pause

When the agent needs a policy choice before acting, stack an approval-gate (why paused + exact action) with an ask-user-question (bounded options). The host still owns authorization and whether work resumes.

Pattern:

approval-gate header and action line state the pause. ask-user-question options carry the judgement. Pair with question resume for the answered → resumed handoff. Message copy / regenerate footers stay host button actions inside chat-message.

Source map

Compose the tool call and approval gate when the next action has a meaningful consequence. Keep the trust boundary in the host.