Live forms and suggestion dismissal

Status: Current | Last reviewed: 2026-09-30

A Form Filling copilot can receive a replacement form definition during an active call. The host sends the definition through the Embedded SDK, the TypeScript SDK, or the conversation API. The model extracts values from the transcript into the selected card.

For a native UI, omit the form's card and consume its SUGGESTION events with subscribeToEvents. Fields still include confirmation and confidence metadata. The Salesforce wrapper forwards these through TrueServeCopilotEvent__c. After a successful Salesforce save, send a targeted form_context command with confirmed fields. A partial save can confirm those values while extraction continues; send form_control: "complete" only when the form is reviewed.

Configure the copilot

  1. Open Form Filling on the Copilot canvas.
  2. Select Card catalogs, then select one or more Available form cards.
  3. Open each form tab and choose its Card. A single available card is used automatically. Map response fields to card properties supports a custom card; matching property names map automatically.
  4. Choose Instances per conversation: Once or Repeatable. Once is the default.
  5. Choose Complete when: Agent confirms completion (default) or Required fields are filled. Automatic completion needs at least one required field, a non-empty value for every required field, and confidence of at least 0.5 for every proposed value. An agent-confirmed value has confidence 1.
  6. Enable Allow forms pushed during the call to accept replacement definitions. Static fields are optional when this is enabled.
  7. Copy the Copilot ID and Form ID shown in the inspector. Publish the agent before starting a call with this configuration.

Each configured form extracts independently and has its own card and completion state. Completed instances stop updating. Repeatable permits an explicit new_instance command after completion; it does not create another record automatically. The next instance has a separate card and reads only conversation turns received after that command. This prevents the same facts from filling multiple records.

A pushed definition replaces the fields of one configured form; it does not create another copilot or change its model, card, mappings, or completion policy. It clears that instance's proposed and confirmed values. The stable form ID remains unchanged even when form_name changes. The runtime includes the current field definitions alongside custom extraction instructions.

Push forms during a call

Send updates after the embed connects to the active conversation and transcription has started. These are live commands, not a way to edit a completed conversation or persist a new agent configuration. Always supply a target when multiple forms are configured. An untargeted update is accepted only when the conversation has one form worker.

const target = { copilot_id: "forms", form_id: "intake" };
const definition = {
  form_name: "Delivery details",
  fields: [
    { name: "recipient", type: "text", description: "Recipient's full name", required: true },
    { name: "address", type: "text", description: "Complete delivery address", required: true },
    { name: "delivery_day", type: "text", description: "Preferred delivery day",
      options: [{ value: "weekday" }, { value: "weekend" }] }
  ]
};

Field names are stable extraction keys. Values are returned as strings; type and description guide extraction. options provides permitted choices to the model. Use field descriptions to explain how to distinguish similar values.

Embedded SDK

Load trueserve-embed-sdk.js from your surface-app deployment and use your existing signed-token embed setup. The embed origin and host origin must be authorized. See Embedding agents for setup.

const embed = await TrueServeEmbed.create({
  container: "#copilot",
  surfaceUrl: "https://surface-app.example.com",
  tokenFetcher: async () => {
    const response = await fetch("/your-backend/embed-token");
    return (await response.json()).embedToken;
  },
  conversationId,
});

// After connection and the first transcript arrives:
await embed.updateFormSchema(definition, target);
await embed.confirmFormField("recipient", "Sam Lee", target);
await embed.controlForm("pause", target);
await embed.controlForm("resume", target);
await embed.completeForm(target);
// Only after completion, and only for a Repeatable form:
await embed.startNextFormInstance(target);

The methods return promises. They resolve after the iframe's authenticated API request succeeds, and reject on an API error, bridge timeout, or destroyed embed. Success acknowledges delivery to the API; it does not mean extraction has finished. An invalid target, a disabled dynamic-form setting, or a completed Once form makes the runtime ignore the command. Watch the card for extracted results.

embed.on("connection", ({ isConnected, conversationId }) => ...) reports the connection. The optional final argument to each form method can name a conversation explicitly; normally the embed uses its active conversation. controlForm accepts pause, resume, reset, complete, or new_instance. Reset clears an unfinished instance, returns to the configured fields, and uses only subsequent conversation turns. It cannot reopen a completed instance.

TypeScript SDK

For a host using TrueServeSDKClient, the optional conversation ID comes before the form target:

await client.updateFormSchema(definition, conversationId, target);
await client.confirmFormField("recipient", "Sam Lee", conversationId, target);
await client.pauseFormFilling(conversationId, target);
await client.resumeFormFilling(conversationId, target);
await client.completeForm(conversationId, target);
await client.startNextFormInstance(conversationId, target);

The client serializes object values for the session-variable endpoint and uses its existing authentication. The packaged @trueserve/sdk client and surface-app client expose the same methods.

HTTP API

A trusted backend can call conversation-api directly. Use the conversation UUID or the runtime session ID and a tenant-scoped credential authorized for that conversation. Keep the credential on your backend.

const response = await fetch(`${conversationApiUrl}/sessions/${conversationId}/variables`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${tenantApiToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    form_target: JSON.stringify(target),
    active_form: JSON.stringify(definition),
  }),
});
if (!response.ok) throw new Error(`Form update failed: ${response.status}`);

The body is a flat variable map, without a variables wrapper. Send structured values as JSON strings. For a confirmation, send confirmed_fields: JSON.stringify([{ field_name: "recipient", field_value: "Sam Lee" }]). For a control, send form_control: "complete". Include form_target in the same request each time; a target from a previous request is not reused.

This endpoint applies to the local TrueServe copilot runtime. It persists the variables and publishes updates to the conversation's UUID and runtime-session channels. Responses list the accepted variable names. Calls through surface-app use its authenticated /api/sessions/{id}/variables proxy; the Embedded SDK selects that route automatically.

Not applicable and dismissal

Copilot card responses include applicable, reason, and result. When there is no useful suggestion, the model returns applicable: false, a brief reason, and result: null. The runtime reports this status. It does not delete the existing card or erase form values.

The SDK and host control presentation:

// Embedded SDK — global policy, with an optional override for one card:
await embed.setCopilotDismissalPolicy("on_not_applicable");
await embed.setCopilotDismissalPolicy("manual", "copilot-coach");
await embed.dismissCopilot("copilot-coach");

// TypeScript SDK — these presentation methods are synchronous:
client.setCopilotDismissalPolicy("on_not_applicable");
client.setCopilotDismissalPolicy("manual", "copilot-coach");
client.dismissCopilot("copilot-coach");
client.on("copilot_status", ({ surfaceId, applicable, reason }) => {
  // Optional host-specific presentation.
});

Use the surfaceId from UI render events when dismissing cards. Form surfaces include an instance number; the configuration's form ID is used for form commands, not for dismissal.