Result Receipt

What the agent just did, with undo while it's still possible.

Pattern

≈ 1,081 tokens per use (tool 767 · guidance 230 · typical payload 84)How token costs are estimated

Example: Result Receipt

Pause the three worst ads from the fall sale.

Paused 3 ads

Meta ad account: Acme Retail

  • Fall sale · Carousel A
  • Fall sale · Video 15s
  • Prospecting · Static 2
Completed at

When to use

  • The agent just completed an action in an external system (paused ads, updated a record, sent an invoice), and the user should see exactly what changed.
  • The system lets the action be reversed for a while, and the user should be able to undo it.

It's the counterpart of Change Preview: the preview asks before acting, the receipt reports after.

When not to use

  • The action hasn't run yet. Use a Change Preview to ask first.
  • The action failed. Say what went wrong in text, or use a Callout.
  • Nothing changed. Say so in a sentence.

How the agent should use it

  • Only show it after the action's tool returned success, and fill items from that result.
  • Write the title in the past tense, with the count: "Paused 3 ads".
  • Only set undo when the system can really reverse the action, and set until to the system's real deadline.
  • On undo, reverse the action with the system's own undo, then confirm in text.

Undo

With undo.until, the Undo button shows the time left ("Undo 4:59"), updating every second. At zero the button is disabled and the footer says "Undo expired". Without until, Undo stays available. After a press, the footer shows "Undo requested" until the host reports back.

The countdown isn't announced every second: screen readers hear it as the Undo button's description, and only "Undo expired" and "Undo requested" are announced.

Installation

npx shadcn@latest add https://your-site.com/r/result-receipt.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { resultReceiptTool } from "@/components/genui/result-receipt.schema"

const tools = {
  // Without undo, add `execute: async () => ({ shown: true })`.
  [resultReceiptTool.name]: tool({
    description: resultReceiptTool.description,
    inputSchema: resultReceiptTool.inputSchema,
  }),
}
components/message.tsx
<ResultReceipt
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : undefined}
  result={part.state === "output-available" ? part.output : undefined}
  onResult={(result) =>
    addToolResult({ tool: "show_receipt", toolCallId: part.toolCallId, output: result })
  }
/>

When the user presses Undo, the agent receives:

{ action: "undo" }

With LangGraph

Push it from a graph node with ui.push({ name: "show_receipt", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Pass the stored result back as result. The receipt shows "Undo requested" and never calls onResult again. A receipt reloaded after its until deadline, without an undo, shows "Undo expired".

<ResultReceipt
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
titlestringPast tense: "Paused 3 ads"
target{ label, kind? }?Data-bound. The system or record that changed. kind is read to screen readers only
items{ label, before?, after?, detail? }[]1–20. Data-bound. Shown as before → after; detail is one quiet line under the label (an address, an ID), not a reason
completedAtstringData-bound. ISO 8601
undo{ label?, until? }?Only when the action can really be reversed. until is ISO 8601
link{ label, url }?http(s) only. Opens in a new tab

Host props: status (streaming, ready, undone), onResult, result and className.

Source

On this page