Now Assist Skills Guide
Create and configure Now Assist Skills using the NowAssistSkillConfig API in the Fluent SDK. Skills use configured prompts to generate AI responses from an LLM, optionally enriched by inputs and tools (script, inline script, web search, subflows, flow actions, decision branches). This guide covers the full lifecycle: provider/model selection, input configuration, tool wiring, prompt authoring, security controls, and deployment. Requires SDK 4.6.0 or higher.
When to Use
- Creating or modifying Now Assist Skills using
NowAssistSkillConfig - Adding or modifying skill prompts, inputs, or security controls
- Adding or modifying skill tools (script, inline script, web search, subflows, flow actions, skill-as-tool, decision branches)
- Configuring skill deployment to UI Actions, Flow Actions, or Now Assist Panel
Skill Structure
NowAssistSkillConfig(Arg 1: SkillDefinition, Arg 2: SkillPromptConfig)
API Structure
import { NowAssistSkillConfig } from '@servicenow/sdk/core'
NowAssistSkillConfig(
// Arg 1: SkillDefinition
{
$id: Now.ID['skill_name'],
name: string,
description?: string,
shortDescription?: string,
inputs?: InputAttribute[],
outputs?: OutputAttribute[], // optional — 5 standard outputs auto-generated if omitted
tools?: (t: ToolGraphBuilder) => ToolHandles | void,
securityControls: SecurityControls, // MANDATORY
skillSettings?: SkillSettings,
deploymentSettings?: DeploymentSettings
},
// Arg 2: SkillPromptConfig
{
providers: [{
provider: 'Now LLM Service' | 'Azure OpenAI' | 'Open AI' | string,
providerAPI?: { type: 'sys_hub_flow', id: string },
prompts: [{
name: string,
versions: [{
$id: Now.ID['prompt_v1'],
model: string,
temperature?: number,
maxTokens?: number,
promptState: 'draft',
prompt: string | ((p: PromptBuilder) => string),
filterCondition?: { [inputName: string]: string }
}]
}]
}]
}
)
Provider and Model Configuration
The GenAI configuration follows a 3-level hierarchy: Provider -> Provider API -> Model.
Query Steps
- Query
sys_gen_ai_providerfor provider display names - Query
sys_generative_ai_provider_mappingwithexternal=true^active=truefor Provider APIs - Query
sys_generative_ai_model_configfor each mapping withprovider=<mapping_sys_id>^active=true
Key Field Mappings
| What You Need | Table | Column | Code Property |
|---|---|---|---|
| Provider name | sys_generative_ai_provider_mapping | provider (display) | provider key |
| Provider API flow | sys_generative_ai_provider_mapping | provider_implementation | providerAPI.id |
| Model identifier | sys_generative_ai_model_config | model | model |
Default Recommendation
Use Now LLM Service with Now LLM Generic provider API and llm_generic_small_v2 model.
Code Example
providers: [{
provider: 'Now LLM Service',
providerAPI: {
type: 'sys_hub_flow',
id: '<provider_implementation_sys_id>'
},
prompts: p => ({
model: 'llm_generic_small_v2',
// prompt config
})
}]
Known Providers
'Now LLM Service', 'Azure OpenAI', 'Open AI', 'Google Gemini', 'AWS Claude', 'IBM Watson', 'Perplexity', 'Aleph Alpha', 'Custom LLM Provider'
Input Attributes
| Property | Required | Description |
|---|---|---|
$id | Yes | Unique identifier |
name | Yes | Attribute name (must not contain underscores or special characters) |
description | No | Description of the input attribute |
dataType | Yes | 'string', 'numeric', 'boolean', 'glide_record', 'simple_array', 'json_object', 'json_array' |
mandatory | No | Whether input is required (default: false) |
truncate | No | Whether to truncate the value (only valid for: string, numeric, boolean, glide_record; NOT supported for: simple_array, json_object, json_array) |
testValues | No | Values for testing and validation |
tableName | Conditional | Required when using glide_record dataType |
tableSysId | Conditional | Required when testValues is provided for glide_record type |
Constraint: A skill can have at most one input of type glide_record.
Name conversion: "Incident Number" becomes {{incident_number}} in prompts.
glide_record vs Tools
Use glide_record input (preferred) when:
- Single table query
- No calculations or transformations needed
- No tools need access to the record data
Use tools with string input type when:
- Multiple tables or joins needed
- Calculations or transformations required
- Record data needed inside a tool script
Critical: glide_record inputs are NOT accessible inside tools. They can only be referenced in prompts via p.input.recordName.fieldName.
Output Attributes
Do NOT define custom outputs. The platform automatically creates five default outputs: response, confidence, explanation, metadata, citations. Reference them in prompts with ${p.output.response}.
Tool Configuration
Tool Methods
| Method | Purpose |
|---|---|
t.Script() | Reference Script Include |
t.InlineScript() | Inline script function |
t.WebSearch() | Web search (AI answers) |
t.Skill() | Call another skill |
t.Subflow() | Execute Flow Designer subflow |
t.FlowAction() | Execute Flow Designer action |
t.Decision() | Conditional branching |
Output Access in Prompts
| Tool Type | Access Pattern |
|---|---|
t.Script() / t.InlineScript() | ${p.tool.ToolName.output} |
t.WebSearch() / t.Skill() | ${p.tool.ToolName.response} |
t.FlowAction() / t.Subflow() | ${p.tool.ToolName.outputName} |
Required Identifiers
Every tool needs a $id. Some tools need additional identifiers depending on how they integrate with the platform:
| Tool Type | $id | $capabilityId | output.$id | Per-input $id | Per-output $id |
|---|---|---|---|---|---|
t.InlineScript() | Yes | No | No | No | No |
t.Script() | Yes | Yes | Yes | Yes | No |
t.WebSearch() | Yes | No | No | No | No |
t.Skill() | Yes | No | No | No | No |
t.FlowAction() | Yes | Yes | No | Yes | Yes |
t.Subflow() | Yes | Yes | No | Yes | Yes |
t.Decision() | Yes | No | No | No | No |
$id: Unique record identifier for the tool itself.$capabilityId: Identifies the external capability (Script Include, Flow Action, or Subflow) the tool wraps. Required because these tools reference a platform artifact that exists outside the skill definition.output.$id: Script tools produce a custom output attribute that needs its own identifier. Only applies tot.Script().- Per-input / per-output
$id: Each entry in theinputsoroutputsarray is a separate mapping record and needs its own unique$id.
InlineScript Tool
const getIncident = t.InlineScript("GetIncident", {
$id: Now.ID["skill_getincident_tool"],
script: `(function(context) {
var gr = new GlideRecord('incident');
gr.addQuery('number', context.getValue('incident_number'));
gr.query();
if (gr.next()) {
return {
number: gr.getValue('number'),
short_description: gr.getValue('short_description')
};
}
return { error: 'Incident not found' };
})(context)`
});
Access inputs via context.getValue('input_name') with snake_case conversion.
Script Tool
Requires an external Script Include with accessibleFrom: 'all'. Each inputs[].name must match a function parameter name exactly.
const myTool = t.Script("MyTool", {
$id: Now.ID["my_skill_script_tool"],
$capabilityId: Now.ID["my_skill_script_capability"],
output: { $id: Now.ID["my_skill_script_output"] },
scriptId: myScriptInclude,
scriptFunctionName: 'processData',
inputs: [
{ $id: Now.ID["tool_input_id"], name: 'param', value: t.input.description }
]
});
value only accepts: t.input.inputName, "hardcoded string", or previousTool.output.
WebSearch Tool
const searchWeb = t.WebSearch("SearchWeb", {
$id: Now.ID["my_skill_search_tool"],
searchType: "ai_answers",
query: t.input.userQuery,
aiSearchProviders: "perplexity" // optional
});
Always use searchType: 'ai_answers'. External providers require API key configuration.
Decision Node
const checkPriority = t.Decision("CheckPriority", {
$id: Now.ID["skill_decision"],
depends: [getData],
targets: ["Escalate", "Standard"] as const,
branches: targets => [{
name: "Critical",
to: targets.Escalate,
condition: { field: getData.priority, operator: "is", value: "1" }
}],
default: targets => targets.Standard
});
Branch conditions: { field, operator, value } where operator is 'is' or 'is_not', and value is a plain string.
Tool Chaining
Use the depends property:
const searchWeb = t.WebSearch("SearchWeb", {
$id: Now.ID["search_tool"],
searchType: "ai_answers",
query: getIncident.output,
depends: [getIncident]
});
Prompt Configuration
Mandatory 4-Section Structure
## Role
You are a [specialist type]. Your task is to [objective].
## Context
The user's [input]: '{{input_name}}'
Tool output: '${p.tool.ToolName.output}'
## Instructions
1. [First step]
2. [Second step]
3. [Constraints]
## Output
The output should be [format]. It must be [qualities].
Prompt Versions
| Property | Required | Description |
|---|---|---|
$id | Yes | Unique identifier |
model | Yes | Always 'llm_generic_small_v2' for Now LLM Service |
promptState | Yes | Always 'draft' |
prompt | Yes | Text or builder function (p) => string |
temperature | No | 0.0-1.0 (default: 0.2) |
maxTokens | No | Required for non-Now LLM providers |
filterCondition | No | Per-version usage conditions |
Type-Safe Prompt Builder
prompt: p => `## Role
You are an incident specialist.
## Context
Incident details: ${p.tool.GetIncident.output}
## Instructions
1. Review the incident data.
2. Provide actionable recommendations.
## Output
Provide a structured analysis with Summary, Root Cause, and Recommendations.`
maxTokens Guidelines
| Output Type | Recommended maxTokens |
|---|---|
| Short answers (yes/no, category) | 100-200 |
| Brief summaries | 300-500 |
| Formatted reports | 1000-2000 |
Warning: Setting maxTokens too low for formatted output forces raw JSON responses.
Conditional Prompts (filterCondition)
versions: [
{
$id: Now.ID["prompt_high"],
model: "llm_generic_small_v2",
promptState: "draft",
prompt: p => `High priority analysis: ${p.input.description}`,
filterCondition: { priority: "1" }
},
{
$id: Now.ID["prompt_default"],
model: "llm_generic_small_v2",
promptState: "draft",
prompt: p => `Standard analysis: ${p.input.description}`
}
]
Keys must exactly match input attribute names (case-sensitive, including spaces).
Security Controls (Mandatory)
userAccess
| Type | Configuration | Use Case |
|---|---|---|
| Authenticated | { $id: Now.ID['ua'], type: 'authenticated' } | General, non-sensitive |
| Role-Based | { $id: Now.ID['ua'], type: 'roles', roles: ['itil'] } | Sensitive data |