Skip to main content

Widgets

AI Integration

sn_aiux is the first ServiceNow ux framework where widgets are first-class participants in conversational AI flows. Two widget-record fields and two runtime APIs do most of the work:

  • best_for — natural-language hint the AI agent reads to decide when to invoke the widget.
  • client_tools — JSON manifest of UI actions the widget exposes to the agent at runtime.
  • this.aiContext / setAiContext(ctx) — per-instance AI context the widget can write into.
  • $aiux.getWidget(widgetId, options?) — server-side composition primitive that lets one widget pull another widget's fully-populated data, enabling widget orchestration.

This page covers each in turn.

best_for

A field on the sys_aix_widget record. It's a free-text description engineered for the agent's decision-making, not the developer's — written so an LLM reading the field list can match a user's intent to the right widget.

Example, from the OOB Activity Stream widget:

Displaying ticket or record activity history with comments, work notes, and attachments in a chronological timeline. Ideal for case management, incident tracking, and any record that needs a conversation-style activity feed with the ability to post new entries.

Write best_for the way you'd write a tool description for an agent prompt: be concrete about what the widget shows, what kind of records it works on, and when an agent should reach for it instead of something else.

client_tools

A JSON field on the widget record that declares a set of UI actions the widget exposes to AI agents. Each tool has a description (read by the LLM to decide whether to call it), an arguments schema, and a JS handler bound to the widget instance.

Example, from Activity Stream:

{
  "postComment": {
    "description": "[IMMEDIATE ACTION - UI CONTROL] Posts a comment to the activity stream. EXECUTE THIS TOOL when user wants to add a comment, note, or message to the ticket/record. This posts to the journal field (comments or work notes depending on configuration). Keywords: comment, post, add note, reply, respond, message.",
    "arguments": [
      { "name": "input", "type": "string", "description": "The comment text to post to the activity stream", "required": true }
    ]
  }
}

The widget's Lit class registers a matching static client_tools entry so the framework can dispatch the call into a real JS method when the agent fires it:

class ActivityStream extends AIUXWidgetElement {
  static client_tools = {
    postComment: {
      definition: ActivityStream.prototype.post,
      description: '[IMMEDIATE ACTION - UI CONTROL] Posts a comment to the activity stream. EXECUTE THIS TOOL when user wants to add a comment, note, or message to the ticket/record.',
      arguments: [
        { name: 'input', type: 'string', description: 'The comment text to post', required: true }
      ]
    }
  };
}

Description conventions

OOB widgets share a few writing patterns worth copying:

  • [IMMEDIATE ACTION - UI CONTROL] prefix when the agent should call the tool eagerly, without confirmation.
  • Keyword list at the end (Keywords: comment, post, add note, reply, respond, message.) — explicit triggers the LLM can match against user phrasing.
  • EXECUTE THIS TOOL when ... — capitalized, imperative, action-trigger phrasing.

What OOB widgets expose

A non-exhaustive tour of client_tools declarations across the OOB library:

WidgetTools
Activity StreampostComment
Add attachmentsparameters, properties
Breakout Gamereset-game, pause-game, get-game-state, set-difficulty
Employee Profile CardloadProfileDetails
People CardshowOrgChart, refresh

The Breakout Game is a deliberate teaching example — four tools that map cleanly to natural user requests ("reset the game", "pause the game", "what's the score?", "make it harder") and the implementation is small enough to read in one sitting.

this.aiContext and setAiContext(ctx)

Each widget instance gets an aiContext slot the framework reads when constructing agent prompts. Use this.setAiContext(ctx) to write a structured object describing the widget's current state.

firstUpdated() {
  this.setAiContext({
    record: { table: 'incident', sys_id: this.sysId },
    state: this.data?.state,
    assignee: this.data?.assignee?.display_value,
  });
}

When the agent has access to the widget, that context is part of the prompt — letting the LLM make better decisions about which client_tools to invoke and with what arguments. Update the context whenever the widget's state changes.

$aiux.getWidget — server-side composition

The composition primitive that Service Portal had as $sp.getWidget, preserved and renamed for the new framework. Inside a widget's server script:

const childData = $aiux.getWidget('people-card', {
  user_sys_id: gs.getUserID(),
});
// childData = { properties: {...}, tagName: 'people-card' }

$aiux.getWidget executes another widget's server script with the supplied options and returns its data and rendered properties. You can then either:

  • Render the returned tagName directly in your Lit template, or
  • Use the returned properties to compose a custom UI that includes the other widget's data without rendering its template.

This is how the AIUX equivalent of <sp-widget> works under the hood. It also lets agentic widgets orchestrate other widgets server-side without round-tripping through the client.