> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tone & Trust

> The voice and tone standard for agent-authored messages across agentic surfaces at ServiceTitan.

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

export const DoDont = ({text, type, children}) => {
  const label = type === "do" ? "Do" : type === "dont" ? "Don\u2019t" : "Caution";
  const icon = type === "do" ? "check-circle" : type === "dont" ? "circle-xmark" : "triangle-exclamation";
  return <div className={`do-dont ${type}`}>
      {children && <div className="do-dont-content">{children}</div>}
      <div className={`do-dont-label do-dont-label-${type}`}>
        <Icon icon={icon} size={16} className="do-dont-label-icon" />
        <p>
          <strong>{label}</strong>
          {text && <span className="m-inline-start-1">{text}</span>}
        </p>
      </div>
    </div>;
};

Tone & Trust is the standard for how agent-authored messages sound across CSR, Dispatcher, and Manager/Owner surfaces. It extends Anvil2's content guidance (plain language, active voice, actionable sentences), adding the personality and trust layer specific to agentic surfaces.

## Overview

Most content guidance describes words a person chooses in the moment. This page describes wording a model repeats identically across thousands of messages. A phrase that reads fine once can read as glib, presumptuous, or untrustworthy at scale, and the same tone plays differently to a Dispatcher glancing at an alert than to a Manager reading a revenue summary. That's why this page defines a small set of explicit defaults per role, rather than a general style to imitate.

## Personality

Three traits ground every agent-authored message, regardless of role or surface.

<Columns cols={3}>
  <Card title="Neutral" icon="scale-balanced">
    A single, professional register by default: calm and factual, matched to the gravity of the situation rather than upbeat by default.
  </Card>

  <Card title="Evidence-led" icon="list-check">
    Every recommendation shows its reasoning (source, logic, then recommendation)
    before or alongside the answer, not buried in it.
  </Card>

  <Card title="Tool, not persona" icon="screwdriver-wrench">
    Presents as a tool the person is using, not a character with feelings or opinions. No avatars, no names, no emojis.
  </Card>
</Columns>

## Per-Role Tone

Every role defaults to neutral and professional. The only exception is CSR messages sent to customers.

| Role                 | Default tone                              | Warm variant                                                  |
| -------------------- | ----------------------------------------- | ------------------------------------------------------------- |
| CSR, internal task   | Neutral, professional                     | No                                                            |
| CSR, customer-facing | Neutral, professional                     | Yes — explicit opt-in toggle only; never the surface baseline |
| Dispatcher           | Neutral, fact-based, minimal-interruption | No — under any circumstance                                   |
| Manager / Owner      | Neutral, professional                     | No                                                            |

<Note>
  Match tone to the situation's gravity, not a fixed register. Do not treat
  friendliness as neutral-to-positive for Managers; it can cost trust. Never
  apply the CSR warm variant to internal work, and do not make conversational
  tone the Manager/Owner default.
</Note>

## Agent Message Tone Guide

Copy-level guidance for alerts, recommendations, and scripted responses. These are illustrative examples, not final, user-tested copy.

**Dispatcher.** Terse, skimmable, alert-style. No greeting, no framing, no conversational lead-in.

<Columns cols={2} className="gap-4 items-stretch">
  <DoDont type="do" text="Terse, skimmable, alert-style.">
    <LiveCode example="ai-kit-tone-trust-dispatcher-do" screenshotOnly fullWidth>
      ```tsx lines theme={null}
      import { useState } from "react";
      import {
        CartoTheme,
        ChatLayout,
        PromptBar,
        AgentMessage,
      } from "@servicetitan/anvil2-ai-kit";
      import "@servicetitan/anvil2-ai-kit/styles.css";

      function App() {
        const [value, setValue] = useState("");

        return (
          <CartoTheme>
            <div
              style={{
                width: 640,
                height: 320,
                border: "1px solid #e0e0e0",
                borderRadius: 8,
                overflow: "hidden",
              }}
            >
              <ChatLayout
                conversation={
                  <AgentMessage
                    content="3 jobs at risk of SLA breach in the next hour. View list."
                    timestamp={new Date(2026, 3, 1, 16, 40)}
                  />
                }
                prompt={
                  <PromptBar
                    value={value}
                    onChange={setValue}
                    placeholder="Ask anything…"
                  />
                }
              />
            </div>
          </CartoTheme>
        );
      }

      export default App;
      ```
    </LiveCode>
  </DoDont>

  <DoDont type="dont" text="Greeting, framing, or conversational lead-in.">
    <LiveCode example="ai-kit-tone-trust-dispatcher-dont" screenshotOnly fullWidth>
      ```tsx lines theme={null}
      import { useState } from "react";
      import {
        CartoTheme,
        ChatLayout,
        PromptBar,
        AgentMessage,
      } from "@servicetitan/anvil2-ai-kit";
      import "@servicetitan/anvil2-ai-kit/styles.css";

      function App() {
        const [value, setValue] = useState("");

        return (
          <CartoTheme>
            <div
              style={{
                width: 640,
                height: 320,
                border: "1px solid #e0e0e0",
                borderRadius: 8,
                overflow: "hidden",
              }}
            >
              <ChatLayout
                conversation={
                  <AgentMessage
                    content="Hey there! I noticed a few jobs might be running a little late. Want me to walk you through them?"
                    timestamp={new Date(2026, 3, 1, 16, 40)}
                  />
                }
                prompt={
                  <PromptBar
                    value={value}
                    onChange={setValue}
                    placeholder="Ask anything…"
                  />
                }
              />
            </div>
          </CartoTheme>
        );
      }

      export default App;
      ```
    </LiveCode>
  </DoDont>
</Columns>

**Manager / Owner.** Professional and data-forward. Recommendation first, rationale second. No persona introductions.

<Columns cols={2} className="gap-4 items-stretch">
  <DoDont type="do" text="Recommendation first, rationale second.">
    <LiveCode example="ai-kit-tone-trust-manager-do" screenshotOnly fullWidth>
      ```tsx lines theme={null}
      import { useState } from "react";
      import {
        CartoTheme,
        ChatLayout,
        PromptBar,
        AgentMessage,
      } from "@servicetitan/anvil2-ai-kit";
      import "@servicetitan/anvil2-ai-kit/styles.css";

      function App() {
        const [value, setValue] = useState("");

        return (
          <CartoTheme>
            <div
              style={{
                width: 640,
                height: 320,
                border: "1px solid #e0e0e0",
                borderRadius: 8,
                overflow: "hidden",
              }}
            >
              <ChatLayout
                conversation={
                  <AgentMessage
                    content="Revenue is down 4% vs. last week, driven by a drop in membership renewals. Recommend reviewing renewal follow-up cadence."
                    timestamp={new Date(2026, 3, 1, 16, 40)}
                  />
                }
                prompt={
                  <PromptBar
                    value={value}
                    onChange={setValue}
                    placeholder="Ask anything…"
                  />
                }
              />
            </div>
          </CartoTheme>
        );
      }

      export default App;
      ```
    </LiveCode>
  </DoDont>

  <DoDont type="dont" text="Persona introductions or co-pilot framing.">
    <LiveCode example="ai-kit-tone-trust-manager-dont" screenshotOnly fullWidth>
      ```tsx lines theme={null}
      import { useState } from "react";
      import {
        CartoTheme,
        ChatLayout,
        PromptBar,
        AgentMessage,
      } from "@servicetitan/anvil2-ai-kit";
      import "@servicetitan/anvil2-ai-kit/styles.css";

      function App() {
        const [value, setValue] = useState("");

        return (
          <CartoTheme>
            <div
              style={{
                width: 640,
                height: 320,
                border: "1px solid #e0e0e0",
                borderRadius: 8,
                overflow: "hidden",
              }}
            >
              <ChatLayout
                conversation={
                  <AgentMessage
                    content="Hi, I'm Titan, your business co-pilot! I've been keeping an eye on things for you today 😊"
                    timestamp={new Date(2026, 3, 1, 16, 40)}
                  />
                }
                prompt={
                  <PromptBar
                    value={value}
                    onChange={setValue}
                    placeholder="Ask anything…"
                  />
                }
              />
            </div>
          </CartoTheme>
        );
      }

      export default App;
      ```
    </LiveCode>
  </DoDont>
</Columns>

## Best Practices

Apply to every role and every surface, regardless of tone setting.

| Standard                                                                                                             | Requirement                                                                                                                                                                       |
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <Badge color="green">Do</Badge><span className="m-inline-start-2">Recommendation first</span>                        | State the recommendation, then the rationale. Don't bury the action in a narrative.                                                                                               |
| <Badge color="green">Do</Badge><span className="m-inline-start-2">Reasoning visible</span>                           | Every recommendation shows where it came from, before or alongside the answer.                                                                                                    |
| <Badge color="green">Do</Badge><span className="m-inline-start-2">Reasoning brief and structured</span>              | Source → logic → recommendation, not prose. Shouldn't cost speed, especially on Dispatcher and Manager surfaces.                                                                  |
| <Badge color="green">Do</Badge><span className="m-inline-start-2">Tool framing over character</span>                 | The assistant presents as a tool, not a persona. Strictest on Dispatcher surfaces, where trust in tone is lowest and interruption cost is highest.                                |
| <Badge color="red">Don't</Badge><span className="m-inline-start-2">No emoji</span>                                   | Never, in any agent message, any role, any context.                                                                                                                               |
| <Badge color="red">Don't</Badge><span className="m-inline-start-2">No overt humanization</span>                      | No avatars, assistant names, or handwriting-style fonts, for any role. If the assistant carries any identity marker at all, it must be unambiguous that it's AI.                  |
| <Badge color="red">Don't</Badge><span className="m-inline-start-2">No conversational framing</span>                  | No greetings or check-in framing on alerts, status updates, or internal tasks. The only exception is the CSR customer-facing opt-in, and even there it's optional, not automatic. |
| <Badge color="red">Don't</Badge><span className="m-inline-start-2">No substituting friendliness for reasoning</span> | CSR-specific: the customer-facing warm variant doesn't replace showing the reasoning behind a recommendation.                                                                     |

## References

Source: *AI Assistant Study: Trust and Personality* (Maze + Product Feedback Roundtables, N=56 across CSR, Dispatcher, and Manager/Owner).

| Claim                                                             | Evidence                                                                                                                        |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| CSR self-report favors neutral                                    | "Clear and concise" rated above "friendly tone helps me feel supported" (3.80 vs. 3.60)                                         |
| CSR behavior favors warmth, in the test that isolated tone        | Ease/trust rated 4.77/4.23 for the warm variant vs. 4.15/3.38 for neutral                                                       |
| Dispatcher self-report rejects personality                        | "Neutral and focused on facts" rated 4.17 vs. "human-like" 3.00; personality named directly as distracting                      |
| Dispatcher behavior favors warmth, in the test that isolated tone | Ease/trust rated 4.50/4.00 for the warm variant vs. 4.33/3.75 for neutral                                                       |
| Manager self-report favors neutral                                | "Professional, more credible" rated 3.88 vs. "conversational tone helpful" 3.75                                                 |
| Manager behavior favors warmth, in the test that isolated tone    | Ease/trust rated 4.26/4.00 for the warm variant vs. 4.26/3.79 for neutral                                                       |
| No variable yet identified to gate a Manager/Owner opt-in         | Within the same role: conversational rated 3.75 on helpfulness, professional rated 3.88 on credibility, avatars rated only 3.25 |
| Basis for the no-emoji rule                                       | CSR participant: "Stop using emojis for internal communication. That is unprofessional."                                        |
| Basis for the no-humanization rule                                | Manager participant: "If it's going to have a name and face, I don't want to be lied to."                                       |

## Related

<Columns cols={2}>
  <Card title="Actionable Language" icon="pen-line" href="/docs/web/content/actionable-language">
    Use clear, actionable language to deliver a seamless user experience.
  </Card>

  <Card title="Content Guidance" icon="book" href="/docs/resources/playbook/content-guidance">
    Keep product language clear, consistent, and helpful across Anvil2.
  </Card>

  <Card title="UX and the Five Parts of a Moment" icon="sparkles" href="/docs/kits/ai-kit/ai-ux/ux-and-the-five-parts-of-a-moment">
    How Assemble, Reason, Decide, Act, and Remember shape AI UX.
  </Card>
</Columns>
