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. No file changed.

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. No secrets or production paths.

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. No file changed.</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. Do not treat recipes.css as product CSS.

Install

head.html
<link rel="stylesheet" href="tokens.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}

3 headings found. No file changed.

error host rejected output

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

Permission denied. No file changed.

cancelled host stopped call

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

The host cancelled this call before execution.

Contract and safety

Install
tokens.css, tool-call.css, approval-gate.css, and button.css for actions.
States
Tool calls support running, waiting, success, error, and cancelled. Gates support waiting, success, error, and cancelled.
No JavaScript
Exact arguments, output, action ID, and native decision form remain visible. The host receives the normal POST.
Accessibility
Label both regions, expose status text, use concrete button labels, and never rely on the state dot alone.
Security
Escape arguments and output. Bind approval to an immutable action ID plus exact serialized tool name and arguments.
Host boundary
The component never executes an approved action. The host validates, authorizes, executes, audits, and can invalidate stale approvals.

Compose a judgement pause

When the agent needs a policy choice before acting, do not invent a supervision component. 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 — not a new component.

Source map

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