Skip to content

Writing

The mechanics of Oration copy: sentence case, numbers, dates, labels, errors, empty states and the product glossary.

Case and structure#

Sentence case everywhere, nothing above a heading, and metadata separated by layout rather than punctuation.

The Sentence Case Rule

Everything is sentence case. No eyebrow or kicker label above a heading, no uppercase labels, no letter-spaced captions.

The Layout Over Punctuation Rule

Metadata is never joined into dot-separated strings (A · B · C). Separate it with layout, commas or words.

Payment runs this week

Do. Capitalize the first word and proper nouns only, in headings, buttons, tabs, menus and column headers.

Payment Runs This Week

Don't. Title Case labels, or set them in capitals. It reads as shouting and makes every label look like a name.

Remittance settings

Where suppliers receive payment details.

Do. Start with the heading. If the section needs context, put it in the description below.

Payments

Remittance settings

Don't. Stack a small uppercase kicker above the heading to label it.
  • Proper nouns keep their capitals: Cedarline, Northwind Freight, Agents Platform, Contact Center, Copilot, Nora.
  • Features are not proper nouns: workflows, sequences, enrichment, procedures. See Naming.
  • Headings, labels, buttons, tabs and toast titles take no closing period. Descriptions, helper text and anything with two sentences do.
  • Lowercase after a colon unless what follows is a proper noun or a full sentence of its own.
  • Join two pieces of metadata with a comma or a word (“Net 30, due Oct 2”), or give each its own place in the layout. Never a middle dot, pipe or slash.

Numbers#

Numerals for every count, separators from 1,000, cents wherever money is compared, and tabular figures so columns line up.

One value, four contexts

Type a value. Each line is formatted with Intl.NumberFormat the way Canon writes it in that context.

Table cell, total
$18,240.00
Summary, chart axis
$18.2K
Count
18,240 credits
Share of $24,516.00
74.4%
import { Input } from "@oration/canon/components/input";import { Label } from "@oration/canon/components/label";import * as React from "react";export function NumberFormats() {    const id = React.useId();    const [raw, setRaw] = React.useState("18240");    const value = Number(raw.replace(/[^\d.-]/g, "")) || 0;    const rows = [        {            context: "Table cell, total",            text: new Intl.NumberFormat("en-US", {                style: "currency",                currency: "USD",            }).format(value),        },        {            context: "Summary, chart axis",            text: new Intl.NumberFormat("en-US", {                style: "currency",                currency: "USD",                notation: "compact",                maximumFractionDigits: 1,            }).format(value),        },        {            context: "Count",            text: `${new Intl.NumberFormat("en-US").format(Math.round(value))} credits`,        },        {            context: "Share of $24,516.00",            text: new Intl.NumberFormat("en-US", {                style: "percent",                maximumFractionDigits: 1,            }).format(value / 24516),        },    ];    return (        <div className="flex w-full max-w-md flex-col gap-3 text-left">            <div className="flex flex-col gap-1.5">                <Label htmlFor={id}>Value</Label>                <Input                    id={id}                    inputMode="decimal"                    value={raw}                    onChange={(event) => setRaw(event.target.value)}                    className="tabular-nums"                />            </div>            <dl className="overflow-hidden rounded-xl bg-card shadow-border">                {rows.map((row) => (                    <div                        key={row.context}                        className="flex items-center justify-between gap-4 border-b border-border px-4 py-2.5 text-13 last:border-b-0"                    >                        <dt className="text-muted-foreground">{row.context}</dt>                        <dd className="font-medium text-foreground tabular-nums">                            {row.text}                        </dd>                    </div>                ))}            </dl>        </div>    );}
How to write numbers
KindWriteNotRule
Counts3 invoices, 1,284 suppliersthree invoices, 1284Numerals always in UI, with a comma from 1,000.
Money in tables$18,240.00$18240, 18,240 USDSymbol first, separators, two decimals. A column shows cents on every row or none.
Money in summaries$18.2K, $1.84M$18.2k, 18.2 KCompact only in summaries, stat strips and chart axes. Uppercase K and M, no space.
Negative money-$412.50($412.50), $-412.50Minus before the symbol, as Intl.NumberFormat writes it.
Percentages92.4%, 2%92.40 %, 2 percentNo space, at most one decimal. Write “percent” only in running prose.
ChangesUp 12%, +12%12% increase!!In a sentence say up or down; a delta chip uses a sign and an arrow.
Ranges$5,000 to $10,000, Oct 2 to Oct 9$5,000-$10,000Write “to”. A dense chart axis may use an en dash without spaces: 10–50.
Units450 ms, 1.2 s, 10 MB450ms, 1.2secA space between the number and the unit; percent is the exception.
Credits9,200 of 10,000 credits92% used!Give the count and the limit; the meter shows the share.
Every number that changes or gets compared sits in tabular-nums and aligns right in its column. See the Tabular Figures Rule.

Dates and times#

Relative while it's recent, absolute once it's older than a week, and the full date where a person reads it as a heading.

The same events in three contexts

Switch the context. Rows stay short, sentences read naturally, headings give the full date. Every relative time keeps the exact one in its title.

  • Remittance sent to Northwind Freight
  • W-9 requested from Halcyon
  • Payment run approved
  • Orchard Street disputed INV-20438
  • Halcyon's W-9 expired
  • Next payment run

Now is Monday, September 28, 2:00 PM CT. Hover a time for the full date.

import { SegmentedControl } from "@oration/canon/components/segmented-control";import * as React from "react";export function DateFormats() {    const [context, setContext] = React.useState<        "row" | "sentence" | "heading"    >("row");    // Today in Cedarline's workspace: Monday, September 28, 2026, 2:00 PM CT.    const now = new Date("2026-09-28T14:00:00-05:00");    const events = [        {            what: "Remittance sent to Northwind Freight",            at: "2026-09-28T13:58:00-05:00",        },        { what: "W-9 requested from Halcyon", at: "2026-09-28T11:12:00-05:00" },        { what: "Payment run approved", at: "2026-09-27T16:12:00-05:00" },        {            what: "Orchard Street disputed INV-20438",            at: "2026-09-24T09:30:00-05:00",        },        { what: "Halcyon's W-9 expired", at: "2026-09-01T00:00:00-05:00" },        { what: "Next payment run", at: "2026-10-01T09:00:00-05:00" },    ];    const zone = "America/Chicago";    const time = (date: Date) =>        date.toLocaleTimeString("en-US", {            hour: "numeric",            minute: "2-digit",            timeZone: zone,        });    const short = (date: Date) =>        date.toLocaleDateString("en-US", {            month: "short",            day: "numeric",            timeZone: zone,        });    const long = (date: Date) =>        date.toLocaleDateString("en-US", {            month: "long",            day: "numeric",            timeZone: zone,        });    const full = (date: Date) =>        date.toLocaleDateString("en-US", {            weekday: "long",            month: "long",            day: "numeric",            timeZone: zone,        });    const weekday = (date: Date) =>        date.toLocaleDateString("en-US", { weekday: "long", timeZone: zone });    const calendarDay = (date: Date) =>        Date.parse(date.toLocaleDateString("en-CA", { timeZone: zone }));    const format = (iso: string) => {        const date = new Date(iso);        const minutes = Math.round((now.getTime() - date.getTime()) / 60000);        const days = Math.round(            (calendarDay(now) - calendarDay(date)) / 86400000,        );        if (context === "heading") return full(date);        if (context === "row") {            if (minutes < 0) return short(date);            if (minutes < 1) return "Just now";            if (minutes < 60) return `${minutes} min ago`;            if (days === 0) return `${Math.round(minutes / 60)} hr ago`;            if (days === 1) return "Yesterday";            if (days < 7) return `${days} days ago`;            return short(date);        }        if (minutes < 0) return `on ${full(date)} at ${time(date)} CT`;        if (minutes < 1) return "just now";        if (minutes < 60) return `${minutes} min ago`;        if (days === 0) return `today at ${time(date)}`;        if (days === 1) return `yesterday at ${time(date)}`;        if (days < 7) return `on ${weekday(date)} at ${time(date)}`;        return `on ${long(date)}`;    };    return (        <div className="flex w-full max-w-lg flex-col gap-3 text-left">            <SegmentedControl                label="Context"                value={context}                onValueChange={setContext}                options={[                    { value: "row", label: "Dense row" },                    { value: "sentence", label: "Sentence" },                    { value: "heading", label: "Heading" },                ]}            />            <ul className="overflow-hidden rounded-xl bg-card shadow-border">                {events.map((event) => (                    <li                        key={event.what}                        className="flex items-center justify-between gap-4 border-b border-border px-4 py-2.5 text-13 last:border-b-0"                    >                        <span className="min-w-0 truncate text-foreground">                            {event.what}                        </span>                        <time                            dateTime={event.at}                            title={new Date(event.at).toLocaleString("en-US", {                                dateStyle: "full",                                timeStyle: "short",                                timeZone: "America/Chicago",                            })}                            className="shrink-0 text-muted-foreground tabular-nums"                        >                            {format(event.at)}                        </time>                    </li>                ))}            </ul>            <p className="text-xs text-muted-foreground">                Now is Monday, September 28, 2:00 PM CT. Hover a time for the                full date.            </p>        </div>    );}
Date and time formats
ContextFormatExample
Headings and greetingsWeekday, month, dayMonday, September 28
Dense rows and cellsShort month and daySep 28
Not this yearAdd the yearSep 28, 2025
Times12-hour with minutes, a space, then AM or PM2:00 PM, 9:40 AM
Time zonesAbbreviation after the time, when people in other zones read it2:00 PM CT
Date and time togetherDate, “at”, timeThursday, October 1 at 9:00 AM CT
Under a minuteJust nowJust now
Under an hourMinutes ago12 min ago
Earlier todayHours ago in rows, the time in sentences3 hr ago, today at 11:12 AM
YesterdayYesterday, with the time in sentencesYesterday at 4:12 PM
2 to 6 days agoDays ago in rows, the weekday in sentences4 days ago, on Thursday at 9:30 AM
A week or moreAbsoluteSep 1
FutureAbsolute, with the weekday when it's this weekOct 1, Thursday, October 1
DurationsClock for media, words for spans3:12, 2 hr 15 min
  • Wrap every date in <time dateTime> and put the full date and time in its title or tooltip.
  • Ticketing's dense columns may abbreviate further (5m, 3h, 2d), as Linear does. Nowhere else does.
  • Don't write a date as numbers only (9/28). Month names can't be misread across regions.
  • Payment terms keep their trade form: Net 30, 2/10 net 30.

Buttons, tooltips and links#

An action's label says what happens when you press it. A link's text says where it goes.

Buttons#

Start with a verb and name the object. A confirmation button repeats the consequence, so the dialog can be answered without reading the body.

Do. Verb plus object: the label still makes sense out of context.
Don't. A bare verb, a noun, or OK. The reader has to work out what will happen.
  • One vocabulary per flow. Get started to enter, Continue to advance, Done to finish. Don't alternate Next and Continue.
  • Cancel closes without changing anything. If the cancel button would discard work, call it Discard changes.
  • Use the object's name when there's room: Delete supplier, not Delete. In a dialog about one record, its name goes in the title, not the button.
  • A menu item that opens a dialog asking for more input ends with one ellipsis character: Move to payment run…. Nothing else in a label does.
  • Toggle and switch labels describe the on state: Send remittance advice, not Don't send remittance advice.

Tooltips#

A tooltip names the control in two to four words, in sentence case with no period. Shortcuts follow as keys.

Archive invoiceE
Do. Name the action and the object, then the shortcut.
Click this button to archive the selected invoice so it no longer appears in your list.
Don't. Explain the feature in a sentence, or repeat what the visible label already says.

A tooltip is a name, not documentation. Anything longer, or anything a person needs before they act, belongs in an Info tip or in helper text. Never put a tooltip on a natively disabled control; say why it's off in text beside it.

Placeholders, helper text and errors#

A field tells people what it wants before they type, and tells them how to fix it next to where it broke.

A field written well

The label names the value, the placeholder is an example, the helper text explains it, and the error says what to enter. Submit it empty or with 15.

Percent off the invoice when Cedarline pays within 10 days.

import { Button } from "@oration/canon/components/button";import { Input } from "@oration/canon/components/input";import { Label } from "@oration/canon/components/label";import { toast } from "@oration/canon/components/toast";import { CircleAlertIcon } from "lucide-react";import * as React from "react";export function FieldCopy() {    const id = React.useId();    const [value, setValue] = React.useState("");    const [error, setError] = React.useState("");    return (        <form            noValidate            onSubmit={(event) => {                event.preventDefault();                const percent = Number(value);                if (                    value.trim() === "" ||                    Number.isNaN(percent) ||                    percent < 0 ||                    percent > 10                ) {                    setError("Enter a discount from 0 to 10 percent.");                    document.getElementById(id)?.focus();                    return;                }                setError("");                toast.add({                    type: "success",                    title: "Payment terms saved",                    description: `Halcyon gets ${percent}% off when paid within 10 days.`,                });            }}            className="flex w-full max-w-sm flex-col gap-4 rounded-xl bg-card p-4 text-left shadow-border"        >            <div className="flex flex-col gap-1.5">                <Label htmlFor={id}>Early-pay discount</Label>                <div className="relative">                    <Input                        id={id}                        inputMode="decimal"                        placeholder="2"                        value={value}                        onChange={(event) => setValue(event.target.value)}                        aria-invalid={error ? true : undefined}                        aria-describedby={`${id}-help${error ? ` ${id}-error` : ""}`}                        className="pr-8 tabular-nums"                    />                    <span                        aria-hidden="true"                        className="pointer-events-none absolute top-1/2 right-2.5 -translate-y-1/2 text-sm text-muted-foreground"                    >                        %                    </span>                </div>                <p id={`${id}-help`} className="text-xs text-muted-foreground">                    Percent off the invoice when Cedarline pays within 10 days.                </p>                {error ? (                    <p                        id={`${id}-error`}                        className="flex items-start gap-1.5 text-xs text-destructive"                    >                        <CircleAlertIcon                            aria-hidden="true"                            className="mt-px size-3.5"                        />                        {error}                    </p>                ) : null}            </div>            <div className="flex justify-end">                <Button type="submit">Save terms</Button>            </div>        </form>    );}
What each piece of a field says
PieceJobExample
LabelNames the value in two or three words. Always visible, or sr-only when the context labels itRemittance email
PlaceholderAn example of the format, never instructions and never the label. No ellipsisap@northwindfreight.com
Helper textOne sentence on what the value is for or what's allowed, shown before the mistakeRemittance advice goes here after each payment run.
ErrorWhat went wrong and what to do, phrased as the fixEnter an email address like ap@northwindfreight.com.
Early-pay discount15Enter a discount from 0 to 10 percent.
Do. Say what to enter, in the reader's terms, next to the field.
Early-pay discount15Invalid input. You entered a value out of range.
Don't. Name the failure in system terms, blame the reader, or hide the error in a toast.
  • Errors that aren't about one field name what failed and what to do: “Couldn't save the sequence. Check your connection and try again.”
  • Never blame: “Enter a supplier name”, not “You forgot the supplier name”. Avoid “invalid”, “illegal” and error codes as the message.
  • Phrase hints positively: “Use letters and numbers”, not “Don't use symbols”.
  • If the same error keeps firing, change the interaction instead of the words.

Empty states, confirmations and toasts#

The moments around an action: before there's anything, before something can't be undone, and right after it happens.

Empty states#

Say what this place is for and offer the next action. A search or filter that finds nothing names the query and offers a way out.

A search that finds nothing

The title quotes the query, the sentence suggests what else to try, and the action clears it. Fix the typo to see results return.

No suppliers match “Halycon”

Check the spelling, or search by tax ID or remittance email.

No suppliers match

import { Button } from "@oration/canon/components/button";import { EmptyState } from "@oration/canon/components/data-state";import { Input } from "@oration/canon/components/input";import { SearchIcon } from "lucide-react";import * as React from "react";export function EmptyCopy() {    const id = React.useId();    const suppliers = [        "Northwind Freight",        "Halcyon Packaging",        "Orchard Street Foods",        "Pinecrest Supply",    ];    const [query, setQuery] = React.useState("Halycon");    const results = suppliers.filter((name) =>        name.toLowerCase().includes(query.trim().toLowerCase()),    );    return (        <div className="flex w-full max-w-sm flex-col gap-3 text-left">            <label htmlFor={id} className="sr-only">                Search suppliers            </label>            <div className="relative">                <SearchIcon                    aria-hidden="true"                    className="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"                />                <Input                    id={id}                    value={query}                    onChange={(event) => setQuery(event.target.value)}                    placeholder="Search suppliers"                    className="pl-8"                />            </div>            <div className="min-h-44 overflow-hidden rounded-xl bg-card shadow-border">                {results.length ? (                    <ul>                        {results.map((name) => (                            <li                                key={name}                                className="border-b border-border px-4 py-2.5 text-13 text-foreground last:border-b-0"                            >                                {name}                            </li>                        ))}                    </ul>                ) : (                    <EmptyState                        size="sm"                        illustration="search"                        title={`No suppliers match “${query.trim()}”`}                        description="Check the spelling, or search by tax ID or remittance email."                        action={                            <Button                                variant="outline"                                size="sm"                                onClick={() => setQuery("")}                            >                                Clear search                            </Button>                        }                    />                )}            </div>            <p role="status" className="sr-only">                {results.length                    ? `${results.length} ${results.length === 1 ? "supplier" : "suppliers"}`                    : "No suppliers match"}            </p>        </div>    );}
  • First-run: a title naming what's missing (No sequences yet), one sentence on what it's for, and the action that creates one.
  • Done: say the good news plainly (Nothing on hold), with no action if none is needed.
  • Never park lasting information in an empty state; it disappears the moment content exists.

Confirmations#

Confirm only what can't be undone. The title asks the question with the object's name; the body says what goes and what stays; the button repeats the action.

A confirmation that answers itself

The buttons alone tell you what each choice does: Cancel payment run or Keep run, never Yes and No.

import { Button } from "@oration/canon/components/button";import { ConfirmDialog } from "@oration/canon/components/confirm-dialog";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function ConfirmCopy() {    const [open, setOpen] = React.useState(false);    return (        <>            <Button variant="destructive" onClick={() => setOpen(true)}>                Cancel payment run            </Button>            <ConfirmDialog                open={open}                onOpenChange={setOpen}                destructive                title="Cancel the October 1 payment run?"                description="14 invoices for $86,412.50 won't be paid on Thursday. They go back to Scheduled, and you can add them to the next run."                confirmLabel="Cancel payment run"                cancelLabel="Keep run"                onConfirm={() =>                    toast.add({                        title: "Payment run canceled",                        description: "14 invoices are back in Scheduled.",                    })                }            />        </>    );}

Toasts#

A toast confirms what just happened, in the past tense, with the object's name. Reversible actions offer Undo instead of asking first.

Past tense, with Undo

Remove an invoice and undo it, or send a remittance that fails. The title stands alone when read aloud; the description adds what changed.

import { Button } from "@oration/canon/components/button";import { toast } from "@oration/canon/components/toast";export function ToastCopy() {    return (        <div className="flex flex-wrap items-center justify-center gap-2">            <Button                variant="outline"                onClick={() =>                    toast.add({                        title: "Removed INV-20431 from the run",                        description: "The October 1 run now pays 13 invoices.",                        actionProps: {                            children: "Undo",                            onClick: () =>                                toast.add({                                    title: "INV-20431 is back in the run",                                }),                        },                    })                }            >                Remove from run            </Button>            <Button                variant="outline"                onClick={() =>                    toast.add({                        type: "error",                        title: "Couldn't send the remittance",                        description:                            "Northwind Freight's email bounced. Check the address on the supplier record.",                    })                }            >                Send remittance            </Button>        </div>    );}
Toast copy
KindTitleDescription
SuccessRemittance sentNorthwind Freight's AP team gets it at ap@northwindfreight.com.
ReversibleRemoved INV-20431 from the runWith Undo. The run now pays 13 invoices.
ErrorCouldn't send the remittanceWhat failed and what to do. Stays until dismissed.
In progressSending remittancesNo ellipsis in a toast title. The spinner shows it's working.

Punctuation and abbreviations#

Punctuation should disappear. These are the marks Oration uses, the ones it doesn't, and how to handle short forms.

Punctuation rules
MarkRuleExample
Exclamation pointNever in the UI. Calm copy doesn't need it, and serious copy is undermined by it.Payment run approved.
Em dashNever in the UI. Use a period, a comma, a colon or parentheses.Halcyon is on hold: the W-9 expired.
Serial commaOration doesn't use the Oxford comma. Add one only when a list item contains “and” or the list would misread, or rewrite the list.Suppliers, invoices and remittances
PeriodOn sentences. Not on headings, labels, buttons, tooltips, tags or toast titles.Toast title: Remittance sent
EllipsisOne character (…), never three periods. Only on live progress labels and menu items that open a dialog for more input.Finding email…, Move to payment run…
Quotation marksCurly quotes. Quote what someone typed or said, such as a search query.No suppliers match “Halycon”
AmpersandWrite “and”. Keep & only in a proper name.Terms and conditions
ColonTo introduce a value, a list or an explanation. Lowercase after it.Status: on hold
Middle dot, pipe, slashNever between metadata. Use layout, a comma or a word.Net 30, due Oct 2
Acronyms and abbreviations
FormRuleExample
EverydayUse as is. Readers know them.CRM, CSV, API, URL, PDF, SSO
Domain jargonSpell out once, or explain in an Info tip beside the term.TTFB, STT, ICP, AP
PluralsAdd s with no apostrophe.APIs, W-9s, CSVs
PeriodsNone inside acronyms.AM, PM, US
Units and timeShort forms after a number, with a space.12 min ago, 450 ms, 10 MB
LatinDon't use e.g., i.e. or etc. Write “such as”, “that is” or finish the list.Providers such as Deepgram and Azure

Product glossary#

One word for one thing across all four apps. If a term is here, use it as written; if you need a new one, add it here first.

Oration product glossary
TermDefinitionPreferAvoid
WorkspaceA team's home in Oration: its members, records, settings, billing and credits. Cedarline is a workspace.workspaceorg, account, tenant, instance
RecordOne entry of an object, such as a company, a person, a deal or a ticket. Every record has a page.recordentry, item, entity, object (for one record)
CompanyAn organization Cedarline sells to or works with, stored as a record in the CRM.company, companiesaccount, organization, org
PersonA human at a company, stored as a record. Plural is people.person, peoplecontact, lead, user
DealA potential sale moving through pipeline stages to won or lost.dealopportunity, opp
ListA saved collection of records of one object, chosen by hand or by filter, such as suppliers to call this week.listsegment, group, collection
TableAn enrichment table: rows plus columns that look up, generate or compute values and spend credits.tablespreadsheet, sheet, grid
EnrichmentFilling in missing data on a record from providers or AI, cell by cell. A column enriches; a person runs it.enrichment, enrichdata augmentation, scraping, lookup
CreditThe unit enrichment and AI spend. A workspace has an included balance that resets each month.credit, creditstokens, points, coins
SequenceMulti-step outreach on a schedule: email, LinkedIn, call and task steps, with A/B variants.sequencecadence, drip, campaign
WorkflowAn automation on a canvas: a trigger, then steps and branches that run without a person.workflowautomation (for one), zap, recipe, flow
AgentAI software that does work for the team, such as Nora answering supplier calls. Agents propose; people decide.agent, the agent, itbot, AI teammate, assistant, she or he
RunOne execution of a workflow, an enrichment column or an agent task, with its own status and log.run, test runjob, execution, instance
ProposalA change an agent wants to make, shown with its evidence and waiting for a person to decide.proposalsuggestion, request, action item
ApprovalThe decision on a proposal, and the queue on Home where proposals wait for one.approve, approvalsign-off, accept, OK
CopilotThe one chat surface, in every app. It answers questions and acts through proposals.Copilot, Ask Copilotchatbot, the AI, AI chat
ProcedureStep-by-step instructions an agent follows for one task, such as verifying a caller.procedurescript, SOP, playbook
FlowA conversation graph for an agent, built on a canvas from nodes that say, ask and do.flowworkflow, tree, IVR
KnowledgeDocuments and sources an agent can retrieve from when it answers.knowledge, knowledge basetraining data, memory, corpus
ToolAn action an agent can call mid-conversation, such as looking up an invoice or scheduling a callback.toolfunction, plugin, integration
CampaignOutbound calls by a voice agent to a list, such as overdue W-9 collection.campaignblast, dialer, sequence
QueueAn ordered line of conversations waiting for a person, with its routing rules.queueinbox, bucket, pool
TicketA tracked request that needs follow-up, with a status, an assignee and a priority.ticketcase, issue, incident
MacroA saved reply or set of actions a support agent applies in one step.macrocanned response, template, snippet
CallbackA scheduled return call to a customer. The noun is one word; the verb is two: call back.callback, call backcall-back, return call request
ScorecardThe criteria a conversation is graded on, such as verified the caller or confirmed the amount.scorecardrubric, QA form, checklist
EvaluationA test that grades an agent against a scorecard or scripted conversations before it ships.evaluationeval, test suite, benchmark

Words that look alike

A workflow automates work on a canvas; a flow is an agent's conversation graph. A sequence is CRM outreach to people; a campaign is a voice agent calling a list. A proposal waits for a person; a run is something that already executed.