Record-backed Agent Assist profiles¶
Status: Current | Last reviewed: 2026-08-12
Record-backed Agent Assist renders long-lived CRM data in the same surface-app rail used by live copilot cards. It is for asynchronous channels such as Salesforce Email, where there may be no active agent conversation to publish a card.
Runtime shape¶
sequenceDiagram
participant SF as Salesforce Agent Assist LWC
participant AM as ai-manager
participant SA as surface-app rail
SF->>AM: mint-session(assistProfile, optional agent/conversation)
AM-->>SF: JWT with tenant + profile claims
SF->>SA: mount railOnly iframe
SA->>AM: published profile bootstrap via signed JWT
AM-->>SA: profile config + signed catalog manifest
SF->>SA: set-assist-snapshot
SA->>SF: assist-snapshot-request (listener-ready retry)
SF->>SA: set-assist-snapshot
SA-->>SF: ready (profile + snapshot settled)
SA-->>SF: external-event(name, payload)
SF->>SF: RefreshView after host action changes the record
SF->>SF: rerun TrueServe_Context_* Flow
SF->>SA: set-assist-snapshot (updated context)
The source modes are:
| Mode | Profile catalog components | Conversation/copilot cards |
|---|---|---|
| Live Conversation | No | Yes |
| Host Record | Yes | No conversation is created |
| Hybrid | Yes | Yes |
| Auto | Whichever sources App Builder configured | Whichever sources App Builder configured |
The Salesforce Agent Assist preset always uses railOnly=true; surface-app omits transcript, context chrome, view/translation toolbars, composer, and the standalone sidebar. Hybrid mode combines both event sets before the existing layout.region="rail" partition and ordering pass.
Profile lifecycle¶
Profiles live in tenant-scoped agent_assist_profiles. App Builder stores only the stable slug. The slug is generated from the profile name when it is created, and the editor opens immediately. Authors edit draft_config; runtime resolves only published_config, so an incomplete edit cannot change an active host page. Publishing validates that every selected catalog is a ready, published agent_assist catalog with at least one component.
The editor is Tools & Templates → Agent Assist. The author selects one or more catalogs rather than composing individual rail cards. Runtime inherits every component in catalog order. Profiles are host- and object-agnostic; record identity and host-specific context arrive with each embed session instead of being stored in the profile.
The editor's inherited-rail canvas also acts as a data-contract inspector. It derives each card's display title, order, required and optional props, descriptions, defaults, visible_when condition, and explicit bindings from the same component schema used by runtime. It also produces a copyable comma-separated list of suggested lower-camel-case Salesforce Flow output variables, excluding props supplied by explicit record.* bindings. The inspector is derived view state only and is never persisted into the profile configuration.
The built-in Email Copilot catalog provides four record-backed components: Email Case Card, Disposition Table, Evidence Strip, and Draft Response Card. Communication and translation are displayed in the full-width Salesforce Communications timeline; disposition and evidence use the durable workflow artifacts; Draft Response emits draft-response.open with the stored recipient, policy guidance, working draft, and localized draft. Its rail title is Reply recommendation; the component body carries the response type, rationale, and suggested tone instead of repeating a generic “Suggested action” label. It is separate from Copilot Cards, which contains the five live-conversation renderers. Both are single catalogs; cloned catalog rows owned by other tenants must never leak into the active tenant's profile editor.
Automatic data binding¶
The Salesforce wrapper runs its allow-listed TrueServe_Context_* Flow and sends the outputs as context in set-assist-snapshot; record identity and object metadata are also available. For each component prop declared in props_schema.properties, surface-app tries, in order:
- a component-scoped context object named for the component or inherited surface;
- a top-level context value with the same normalized name;
- a record value with the same normalized name.
Normalization ignores case, underscores, dashes, and spaces, so latest_translation binds to latestTranslation. A component stays hidden until all fields in its JSON Schema required array are available. A catalog can also declare x-trueserve-agent-assist.visible_when with the name of a boolean prop; false suppresses the entire rail item, including its rail title. Catalog authors can override a title, order, defaults, visibility, or an exceptional binding in props_schema["x-trueserve-agent-assist"]; those details stay with the reusable component rather than every profile.
For the PagerDuty adapter, hasUnansweredIncomingEmail is true only when the newest relevant EmailMessage is inbound; only sent outbound messages count as replies, while drafts do not. That value drives visible_when, so the recommendation disappears after an agent sends a response.
One-time AI recommendations¶
Response guidance should be generated on the email event, not on every Case-page render. A Salesforce Pub/Sub workflow can trigger once for each incoming EmailMessageChangeEvent, use the EmailMessage id as its idempotency key, and return responseType, summary, and tone alongside the durable translation. The PagerDuty adapter reads those values from the email's Recommendation object in Translations.json; older artifacts fall back to deterministic guidance based on Case priority and status. The record page therefore performs no model call: it only reads the stored result, and an outbound reply independently hides the card.
Snapshot delivery uses a two-way handshake. Salesforce attaches its listener before navigating the iframe and sends the snapshot when the embedded surface reports ready. The surface also requests the snapshot after installing its inbound listener, retrying briefly until one arrives. This makes record-backed cards insensitive to iframe/React startup timing while preserving the same origin and embed-id checks on every message.
The wrapper also registers a Salesforce RefreshView handler. When a sibling host adapter changes the record and dispatches RefreshEvent—for example, after the email composer successfully sends a reply—the wrapper reruns its TrueServe_Context_* Flow and posts the updated snapshot into the existing iframe. It does not remint the session or remount the rail. Consequently, hasUnansweredIncomingEmail=false removes the reply recommendation as part of the same page refresh, without a browser reload or a second loading stencil.
The rail has an explicit three-phase presentation state: loading, content, or empty. A record-backed rail remains loading until its published profile catalogs and host snapshot settle; missing required component props then resolve to the empty state instead of flashing empty while data is still in flight. The Salesforce wrapper paints a rail-shaped stencil before iframe navigation, keeps the iframe transparent until ready, and crossfades the fully rendered rail over that stencil. Reduced-motion preferences disable both the pulse and the transition.
External events¶
There is no profile capability registry or host-action toggle. A catalog component emits an ordinary A2UI action. When that action originates from an assist-profile:* surface, surface-app forwards its action name, label, context, and data model as an external-event; it does not execute host code.
The Salesforce wrapper relays this through a bubbling, composed externalevent DOM event and the exposed TrueServe External Event Lightning Message Channel. Managed-package publishers use the namespaced trueservedev__TrueServeExternalEvent__c channel; an org-local adapter that must also support unpackaged development builds subscribes to both it and TrueServeExternalEvent__c. A PagerDuty package can subscribe to an event such as draft-response.open and launch its existing composer modal. Adding a net-new event therefore means shipping the action in a catalog component and implementing it in the host adapter—there is no TrueServe registry change. The original hostactionrequest and TrueServe Host Action channel remain as compatibility paths for profiles published by the first editor.
The Embecta Case page mounts trueServeEmailActions as that host adapter. It accepts only draft-response.open events for its own record and opens trueServeEmailComposer with the one-time workflow result already stored on TrueServe_Email_Analysis__c. The modal shows one editable English response and explains that it will be translated before delivery; it does not call a model while opening.
Translate & send asks TrueServeEmailService to queue the tenant-managed translation and delivery workflow. The modal closes as soon as Salesforce durably stores the approved draft and enqueues the work. The analysis status changes to Translating and delivering; because processingStatus is already part of the Context Flow, the same Draft Response catalog component replaces its controls with background progress. The invisible host adapter polls the analysis and survives page refreshes by resuming any active delivery it finds. Salesforce creates the sent EmailMessage and changes the analysis to Sent only after the workflow returns send_email.status=ok. The EmailMessage TextBody retains the English working draft, DCC_Email_Language__c is English, and the DCC translated subject/body contain the localized content delivered to the customer. The poller then dispatches RefreshView; the rail reloads its Flow outputs and removes the Reply recommendation. Failures change the status to Send failed, preserve a bounded error, and restore the card's review/retry action.
The durable adapter falls back to TrueServePagerDutyAssistContext when a Case has no TrueServe_Email_Analysis__c, preserving older PagerDuty translation Cases. Legacy PagerDuty-specific composer workflows can remain installed, but the Embecta Case page's generic Email Copilot path does not depend on their provider JWT credentials.
Authorization¶
- Salesforce never receives a tenant API token; Apex mints the embed JWT through its External Credential.
- The JWT has an
approfile slug claim and a tenant claim. Profile-only sessions use a syntheticassist:<profile-id>subject solely as the multi-iframe cookie key. - The runtime bootstrap verifies the JWT locally, requires the requested slug to equal
ap, executes under tenant RLS, and returns only the published profile plus expiring catalog URLs. - External events are inert transport messages. Only an explicitly installed host listener can perform the requested operation.
See also CRM embed components, A2UI rendering, and Salesforce components.