Host capabilities¶
Status: Current | Last reviewed: 2026-05-28
A surface-app agent can suggest host actions — UI actions the host page should carry out, like "open the email composer," "scroll to record X," "show a modal." For those suggestions to work, the host must tell surface-app which actions it supports. That's what set-capabilities and context-update do.
Why capabilities exist¶
Without capability advertisement, an agent has two bad choices:
- Suggest every host action it knows about, half of which the host can't actually do, and have the user click a dead button.
- Suggest no host actions, and miss helpful in-product nudges.
By advertising capabilities, the host narrows the agent's suggestions to ones that will work. The agent's prompt is augmented with the capability list at runtime; suggestions outside the list are filtered before they're rendered.
Capability naming¶
Capability names are dot-namespaced strings:
salesforce.emailComposer.open
salesforce.recordPage.navigate
dashboard.modal.open
dashboard.notification.show
sales.cart.addItem
The convention is <host-family>.<surface>.<action>. Anything stable and unique works — surface-app does no semantic matching; it's a string equality check.
Advertising capabilities¶
After the iframe sends ready, the host posts:
iframe.contentWindow.postMessage(
{
source: "trueserve-sfdc",
type: "set-capabilities",
payload: {
capabilities: [
"salesforce.emailComposer.open",
"salesforce.recordPage.navigate",
"salesforce.utilityBar.expand",
],
hostFamily: "sfdc-lightning",
hostVersion: "60.0",
},
},
embedTokenAllowedOrigin,
);
surface-app accepts the message (origin check + source check pass), updates its capability set, and from this point forward filters tool suggestions accordingly.
You can call set-capabilities more than once. The second call replaces the first. Useful when the host's available actions change as the user navigates.
Reacting to host_action¶
When the agent emits a host-action conversation-event, the host receives:
{
"source": "trueserve-surface-app",
"type": "conversation-event",
"payload": {
"embedId": "...",
"conversationId": "...",
"event": {
"kind": "host-action",
"action": "salesforce.emailComposer.open",
"params": { "to": "lead@example.com", "subject": "Following up" }
}
}
}
The host listens for this, dispatches to its own action handler, and either:
- Performs the action immediately (suggested action becomes invisible to the user — the action just happens).
- Renders a confirmation UI ("The agent suggests sending this email — Send / Edit / Cancel").
Confirmation is the right pattern when the action has user-visible side effects. Auto-execution is the right pattern for navigations and modal opens.
Pushing context¶
Capabilities are static-ish — they describe what the host can do. Context is dynamic — what the user is looking at right now. Push context whenever it changes:
iframe.contentWindow.postMessage(
{
source: "trueserve-sfdc",
type: "context-update",
payload: {
pageContext: {
pageType: "case-detail",
pageLabel: "Case 00001234",
entityId: "5003X00001abcd",
summary: "Open Case for Acme Insurance, opened 2026-05-15, escalated to Tier 2.",
},
componentContext: null,
},
},
embedTokenAllowedOrigin,
);
surface-app folds the summary strings into the agent's system prompt at the next turn. Keep them tight — the LLM has a finite context window, and verbose context burns tokens that could go toward the user's actual question.
Putting it together¶
A complete embed lifecycle:
iframe loads → emits "ready"
host receives "ready" → posts "set-capabilities"
host receives "ready" → posts "context-update" (initial state)
user interacts → host posts "context-update" each time the visible page changes
agent suggests host_action → host receives "conversation-event" with kind="host-action"
host acts on it (or renders confirmation)
Capabilities once, context many times, host actions on demand. That's the pattern.