Function: NowAssistSkillConfig(definition, promptConfig)
Creates a NowAssist Skill configuration
Takes two arguments to solve the IntelliSense ordering problem:
- Arg 1 (definition): Skill metadata, inputs, outputs, tools, security
- Arg 2 (promptConfig): LLM provider configurations with prompts
TypeScript infers function arguments LEFT-TO-RIGHT, so TInputs and TTools
are fully resolved from arg 1 before arg 2 needs them. This guarantees
p.tool.* and p.input.* IntelliSense works regardless of property order.
Parameters
definition
SkillDefinitionWithID<readonly InputAttribute<string>[], Record<string, BaseToolHandle>>
Skill definition including name, inputs, outputs, tools, and settings
Properties:
-
$id (required):
string | number | ExplicitKey<string> -
name (required):
stringDisplay name shown in the NowAssist skill picker and admin views -
securityControls (required):
SecurityControlsSecurity controls for user access and role restrictions MANDATORY: Must be provided for all skills Defines who can access the skill and what roles it can inherit during execution -
deploymentSettings (optional):
DeploymentSettingsDeployment settings for UI Action and UI Builder -
description (optional):
stringDetailed explanation of what the skill does, shown on the skill detail page -
inputs (optional):
readonly InputAttribute<string>[]Input attributes for the skill Define as an inline array — type inference is automatic -
outputs (optional):
OutputAttribute<string>[]Data produced by the skill after execution, available to callers and downstream systems-
tableName:
TTable name for glide_record type (REQUIRED when using glide_record dataType) -
tableSysId:
DbRecord<T> | stringTable sys ID for glide_record type (REQUIRED when using glide_record dataType)
-
-
shortDescription (optional):
stringBrief one-line summary displayed in skill listings and search results -
skillSettings (optional):
SkillSettingsSkill-level settings including providers -
state (optional):
'published'Skill state Set to "published" to publish the skill (state = 1) If not provided, skill will be in draft state (state = 0) -
tools (optional):
(tools: ToolGraphBuilder<readonly InputAttribute<string>[]>) => void | Record<string, BaseToolHandle>Tool graph configuration (optional) Define tools that the skill can use during execution IMPORTANT: Return tool handles to enable type-safe tool output access in prompts! Supported Tool Types:- Script: Script Include execution
- InlineScript: Inline script execution
- FlowAction: Flow Action execution
- Subflow: Subflow execution
- Skill: Skill-as-Tool execution
- WebSearch: Web search capabilities
- Decision: Conditional branching logic
promptConfig
object
Provider and prompt configurations
Properties:
-
providers (required):
[...ValidatedProviders<TProviders, TInputs, TTools>[]] -
defaultProvider (optional):
TProviders[number]['provider']
Examples
Basic NowAssist Skill Example
A minimal skill that processes a user query using a single LLM provider.
/**
* @title Basic NowAssist Skill Example
*
* @description A minimal skill that processes a user query using a single LLM provider.
* This is the simplest possible NowAssist skill configuration.
*/
import { NowAssistSkillConfig } from '@servicenow/sdk/core'
NowAssistSkillConfig(
{
$id: Now.ID['incident_summarizer_skill'],
name: 'Incident Summarizer',
shortDescription: 'Summarizes incident details for quick review',
description:
'Takes an incident number and provides a concise summary of the incident status, priority, and recent updates.',
inputs: [
{
$id: Now.ID['input_incident_number'],
name: 'incident number',
description: 'The incident number to summarize (e.g., INC0012345)',
mandatory: true,
dataType: 'string',
testValues: 'INC0012345',
},
{
$id: Now.ID['input_include_history'],
name: 'include history',
description: 'Whether to include work notes history',
mandatory: false,
dataType: 'boolean',
},
],
outputs: [
{
$id: Now.ID['output_summary'],
name: 'summary',
description: 'The generated incident summary',
dataType: 'string',
},
],
securityControls: {
userAccess: {
$id: Now.ID['acl-basic'],
type: 'authenticated',
},
roleRestrictions: ['2831a114c611228501d4ea6c309d626d'],
},
},
{
providers: [
{
provider: 'Now LLM Service',
prompts: [
{
name: 'Summarize Incident',
versions: [
{
$id: Now.ID['prompt_summarize_v1'],
model: 'llm_generic_small_v2',
temperature: 0.2,
prompt: (p) =>
`You are a ServiceNow incident analyst. Summarize the following incident concisely.\n\nIncident Number: ${p.input['incident number']}\nInclude History: ${p.input['include history']}\n\nProvide:\n1. Current status and priority\n2. Brief description of the issue\n3. Recent activity (if history requested)`,
promptState: 'published',
},
],
},
],
},
],
}
)
Comprehensive NowAssist Skill Example
Demonstrates all configuration options including:
/**
* @title Comprehensive NowAssist Skill Example
*
* @description Demonstrates all configuration options including:
* - Multiple input/output data types (string, boolean, glide_record, json_object, etc.)
* - Tool dependencies and conditional execution
* - Deployment settings (UI Action, Now Assist Panel, Flow Action)
* - Skill settings with preprocessor/postprocessor
* - Multiple providers with multiple prompts and versioning
* - Arrow function prompts with tool output references
* - Published state
*/
import { NowAssistSkillConfig } from '@servicenow/sdk/core'
NowAssistSkillConfig(
{
$id: Now.ID['hr_case_assistant_skill'],
name: 'HR Case Assistant',
shortDescription: 'AI-powered HR case resolution assistant',
description:
'Assists HR agents by analyzing case details, searching knowledge bases, running diagnostic subflows, and generating resolution recommendations with full audit trail.',
// ──────────────────────
// Inputs — all data types
// ──────────────────────
inputs: [
{
$id: Now.ID['input_case_query'],
name: 'case query',
description: 'The HR case question or request',
mandatory: true,
dataType: 'string',
testValues: 'Employee requesting parental leave policy details',
},
{
$id: Now.ID['input_case_record'],
name: 'case record',
description: 'Reference to the HR case record',
mandatory: false,
dataType: 'glide_record',
tableName: 'sn_hr_core_case',
tableSysId: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
},
{
$id: Now.ID['input_include_policy'],
name: 'include policy',
description: 'Whether to include full policy text',
mandatory: false,
dataType: 'boolean',
},
{
$id: Now.ID['input_case_metadata'],
name: 'case metadata',
description: 'Additional case metadata in JSON format',
mandatory: false,
dataType: 'json_object',
},
{
$id: Now.ID['input_related_cases'],
name: 'related case ids',
description: 'List of related case IDs',
mandatory: false,
dataType: 'simple_array',
},
],
// ───────────────────────
// Outputs — multiple types
// ───────────────────────
outputs: [
{
$id: Now.ID['output_recommendation'],
name: 'recommendation',
description: 'AI-generated resolution recommendation',
dataType: 'string',
},
{
$id: Now.ID['output_confidence'],
name: 'confidence score',
description: 'Confidence score of the recommendation (0-100)',
dataType: 'numeric',
},
{
$id: Now.ID['output_policy_refs'],
name: 'policy references',
description: 'Referenced policy documents',
dataType: 'json_array',
},
],
// ─────────────────
// Tools — full graph
// ─────────────────
tools: (t) => {
// Step 1: Search knowledge base for relevant policies
const searchPolicies = t.WebSearch('SearchPolicies', {
$id: Now.ID['tool_search_policies'],
searchType: 'searching_and_scraping',
query: t.input['case query'],
sites: 'hr-policies.company.com',
numResults: 5,
maxTokens: 2000,
country: 'US',
})
// Step 2: Fetch case details via Script Include
const fetchCase = t.Script('FetchCaseDetails', {
$id: Now.ID['tool_fetch_case'],
$capabilityId: Now.ID['tool_fetch_case_cap'],
scriptId: 'aabb1122ccdd3344eeff5566aabb7788',
scriptFunctionName: 'getCaseWithHistory',
inputs: [
{ $id: Now.ID['fetch_case_input_query'], name: 'query', value: t.input['case query'] },
{ $id: Now.ID['fetch_case_input_record'], name: 'caseRef', value: t.input['case record'] },
],
truncate: true,
condition: {
type: 'script',
script: 'return currentInputs.caseRecord !== null && currentInputs.caseRecord !== "";',
},
})
// Step 3: Run compliance check subflow
const complianceCheck = t.Subflow('ComplianceCheck', {
$id: Now.ID['tool_compliance_check'],
$capabilityId: Now.ID['tool_compliance_check_cap'],
subflowId: '1234abcd5678efab9012cdef3456abcd',
inputs: [
{ $id: Now.ID['compliance_input_query'], name: 'requestText', value: t.input['case query'] },
{ $id: Now.ID['compliance_input_region'], name: 'region', type: 'string', value: 'US' },
],
depends: [searchPolicies],
truncate: true,
})
// Step 4: Decision — route based on compliance result
const classifyAction = t.InlineScript('ClassifyAction', {
$id: Now.ID['tool_classify_action'],
depends: [complianceCheck, fetchCase],
script: `function execute(context) {
var compliance = context.getValue("ComplianceCheck.Output");
if (compliance && compliance.indexOf("escalate") >= 0) return { action: "escalate" };
if (compliance && compliance.indexOf("approved") >= 0) return { action: "auto_resolve" };
return { action: "manual_review" };
}`,
})
t.Decision('ActionRouter', {
$id: Now.ID['tool_action_router'],
depends: [classifyAction],
targets: ['AutoResolve', 'EscalateCase', 'ManualReview'] as const,
branches: (targets) => [
{
name: 'Auto Resolve',
to: targets.AutoResolve,
condition: {
field: classifyAction.output,
operator: 'is',
value: 'auto_resolve',
},
},
{
name: 'Escalate',
to: targets.EscalateCase,
condition: {
field: classifyAction.output,
operator: 'is',
value: 'escalate',
},
},
],
default: (targets) => targets.ManualReview,
})
// Step 5a: Auto-resolve via FlowAction
t.FlowAction('AutoResolve', {
$id: Now.ID['tool_auto_resolve'],
$capabilityId: Now.ID['tool_auto_resolve_cap'],
actionId: 'ff001122aabb3344ccdd5566eeff7788',
inputs: [
{
$id: Now.ID['resolve_input_case'],
name: 'case_sys_id',
type: 'string',
value: t.input['case record'],
},
{
$id: Now.ID['resolve_input_notes'],
name: 'resolution_notes',
type: 'string',
value: searchPolicies.response,
},
],
outputs: [
{ $id: Now.ID['resolve_output_status'], name: '__action_status__', type: 'string' },
{ $id: Now.ID['resolve_output_case_number'], name: 'case_number', type: 'string' },
],
depends: [classifyAction],
})
// Step 5b: Escalate to senior HR via Subflow
t.Subflow('EscalateCase', {
$id: Now.ID['tool_escalate'],
$capabilityId: Now.ID['tool_escalate_cap'],
subflowId: 'abcd1234efab5678cdef9012abcd3456',
inputs: [
{ $id: Now.ID['escalate_input_case'], name: 'caseId', value: t.input['case record'] },
{ $id: Now.ID['escalate_input_reason'], name: 'reason', value: complianceCheck.Output },
],
depends: [classifyAction],
})
// Step 5c: Manual review — enrich with additional context
t.InlineScript('ManualReview', {
$id: Now.ID['tool_manual_review'],
depends: [classifyAction],
script: `function execute(context) {
return {
status: "pending_review",
message: "Case requires manual review by HR specialist."
};
}`,
})
},
// ─────────────────────
// Security Controls
// ─────────────────────
securityControls: {
userAccess: {
$id: Now.ID['hr_case_acl'],
type: 'roles',
roles: ['2831a114c611228501d4ea6c309d626d', 'f0b0eb19c30020107acbfa163e040e1c'],
},
roleRestrictions: ['2831a114c611228501d4ea6c309d626d'],
},
// ─────────────────────
// Deployment Settings
// ─────────────────────
deploymentSettings: {
uiAction: { table: 'sn_hr_core_case' },
nowAssistPanel: {
enabled: true,
roles: ['now_assist_panel_user', 'sn_hr_core.manager'],
},
flowAction: true,
skillFamily: 'aabb0011ccdd2233eeff4455aabb6677',
},
// ─────────────────────────────────────
// Skill Settings — pre/postprocessors
// ─────────────────────────────────────
skillSettings: {
providers: [
{
$id: Now.ID['hr_request_validator'],
name: 'TestPreProcessor',
preprocessor: `(function(payload) {
payload.context = payload.context || {};
payload.context.enrichedAt = new Date().toISOString();
return payload;
})(payload);`,
},
{
$id: Now.ID['hr_response_validator'],
name: 'TestPostProcessor',
postprocessor: `(function(payload) {
if (payload.response && payload.response.indexOf('CONFIDENTIAL') >= 0) {
payload.response = '[REDACTED - Contains confidential information]';
}
return payload;
})(payload);`,
},
],
},
},
// ─────────────────────────────────────────────
// Prompt Configuration — multiple providers
// ─────────────────────────────────────────────
{
providers: [
{
// Primary provider: Now LLM Service
provider: 'Now LLM Service',
prompts: [
{
name: 'HR Resolution',
versions: [
// Version 1 — draft (initial)
{
$id: Now.ID['prompt_hr_v1'],
version: 1,
model: 'llm_generic_small_v2',
temperature: 0.2,
maxTokens: 2048,
prompt: (p) =>
`You are an HR case resolution assistant.\n\nUser Query: ${p.input['case query']}\nCase Details: ${p.tool.FetchCaseDetails.output}\nPolicy Search Results: ${p.tool.SearchPolicies.response}\nCompliance Status: ${p.tool.ComplianceCheck.Output}\n\nProvide:\n1. A clear recommendation\n2. Referenced policy sections\n3. Confidence score (0-100)`,
promptState: 'draft',
},
// Version 2 — published (improved)
{
$id: Now.ID['prompt_hr_v2'],
version: 2,
model: 'llm_generic_small_v2',
temperature: 0.15,
maxTokens: 4096,
prompt: (p) =>
`You are an expert HR case resolution assistant with deep knowledge of company policies.\n\n## Context\nUser Query: ${p.input['case query']}\nCase Record: ${p.tool.FetchCaseDetails.output}\nRelated Cases: ${p.input['related case ids']}\n\n## Research\nPolicy Search: ${p.tool.SearchPolicies.response}\nCompliance Check: ${p.tool.ComplianceCheck.Output}\nAction Classification: ${p.tool.ClassifyAction.output}\n\n## Instructions\n1. Analyze the case against company HR policies\n2. Provide a specific, actionable recommendation\n3. List all referenced policy sections as JSON array\n4. Rate your confidence (0-100) based on policy match quality\n\nRespond in structured JSON format.`,
promptState: 'published',
},
],
},
],
defaultPrompt: 'HR Resolution',
defaultPromptVersion: 2,
},
{
// Fallback provider: Azure OpenAI
provider: 'Azure OpenAI',
prompts: [
{
name: 'HR Resolution Fallback',
versions: [
{
$id: Now.ID['prompt_hr_azure_v1'],
model: 'gpt-4o-mini',
temperature: 0.2,
prompt: (p) =>
`Analyze this HR case and provide a resolution recommendation.\n\nQuery: ${p.input['case query']}\nSearch Results: ${p.tool.SearchPolicies.response}`,
promptState: 'published',
},
],
},
],
},
],
defaultProvider: 'Now LLM Service',
}
)
Tools Showcase Example
Demonstrates all available tool types:
/**
* @title Tools Showcase Example
* @description Demonstrates all available tool types:
* - InlineScript: Inline JavaScript execution
* - Script: Reference to a Script Include
* - Subflow: Invoke a Flow Designer subflow
* - FlowAction: Invoke a Flow Designer action
* - WebSearch: Web search (AI answers & searching/scraping variants)
* - Skill: Reference another NowAssist skill as a tool
* - Decision: Conditional branching/routing between tools
*
* Also shows tool dependencies, conditional execution, and prompt tool output references.
*/
import { NowAssistSkillConfig } from '@servicenow/sdk/core'
NowAssistSkillConfig(
{
$id: Now.ID['tools_showcase_skill'],
name: 'IT Support Assistant',
shortDescription: 'Multi-tool IT support skill',
description: 'An advanced skill that routes user queries through multiple tools based on intent.',
inputs: [
{
$id: Now.ID['input_user_query'],
name: 'user query',
description: 'The user support question',
mandatory: true,
dataType: 'string',
testValues: 'My laptop cannot connect to VPN',
},
{
$id: Now.ID['input_priority'],
name: 'priority',
description: 'Issue priority level',
mandatory: false,
dataType: 'string',
},
],
outputs: [
{
$id: Now.ID['output_resolution'],
name: 'resolution',
description: 'The suggested resolution',
dataType: 'string',
},
],
tools: (t) => {
// ─────────────────────────────────────────────
// 1. InlineScript — inline JavaScript execution
// ─────────────────────────────────────────────
const classifyIntent = t.InlineScript('ClassifyIntent', {
$id: Now.ID['tool_classify_intent'],
script: `function execute(context) {
var query = context.getValue("userQuery");
if (query.indexOf("password") >= 0) return { category: "password_reset" };
if (query.indexOf("VPN") >= 0) return { category: "network" };
return { category: "general" };
}`,
})
// ─────────────────────────────────────────
// 2. Script — reference a Script Include
// ─────────────── ──────────────────────────
const fetchUserDetails = t.Script('FetchUserDetails', {
$id: Now.ID['tool_fetch_user'],
$capabilityId: Now.ID['tool_fetch_user_cap'],
scriptId: 'ba03048dec3548c597d6f1698c68969c',
scriptFunctionName: 'getUserProfile',
inputs: [{ $id: Now.ID['fetch_user_input_query'], name: 'query', value: t.input['user query'] }],
truncate: true,
})
// ───── ─────────────────────────────────────────────
// 3. Decision — route based on classification result
// ──────────────────────────────────────────────────
t.Decision('RouteByIntent', {
$id: Now.ID['tool_route_decision'],
depends: [classifyIntent],
targets: ['SearchKB', 'ResetPassword', 'NetworkDiag'] as const,
branches: (targets) => [
{
name: 'Password Issues',
to: targets.ResetPassword,
condition: {
field: classifyIntent.output,
operator: 'is',
value: 'password_reset',
},
},
{
name: 'Network Issues',
to: targets.NetworkDiag,
condition: {
field: t.input['priority'],
operator: 'is',
value: 'high',
},
},
],
default: (targets) => targets.SearchKB,
})
// ────────────────────────────────────────────────────────
// 4. WebSearch (AI Answers) — search the web for solutions
// ────────────────────────────────────────────────────────
t.WebSearch('SearchKB', {
$id: Now.ID['tool_search_kb'],
searchType: 'ai_answers',
query: t.input['user query'],
depends: [classifyIntent],
})
// ──────────────────────────────────────────────────────────
// 5. WebSearch (Searching & Scraping) — scrape specific sites
// ──────────────────────────────────────────────────────────
t.WebSearch('NetworkDiag', {
$id: Now.ID['tool_network_diag'],
searchType: 'searching_and_scraping',
query: t.input['user query'],
sites: 'support.company.com',
numResults: 5,
maxTokens: 2000,
depends: [classifyIntent],
})
// ─────────────────────────────────────────────────────
// 6. Subflow — invoke a Flow Designer subflow
// ─────────────────────────────────────────────────────
t.Subflow('ResetPassword', {
$id: Now.ID['tool_reset_password'],
$capabilityId: Now.ID['tool_reset_password_cap'],
subflowId: '7866867cffc47210151cffffffffff27',
inputs: [{ $id: Now.ID['reset_input_user'], name: 'userId', value: fetchUserDetails.output }],
depends: [classifyIntent, fetchUserDetails],
condition: {
type: 'script',
script: 'return currentInputs.userQuery.indexOf("password") >= 0;',
},
})
// ───────────────────────────────────────────────────
// 7. FlowAction — invoke a Flow Designer action
// ───────────────────────────────────────────────────
t.FlowAction('CreateTicket', {
$id: Now.ID['tool_create_ticket'],
$capabilityId: Now.ID['tool_create_ticket_cap'],
actionId: '65fa39f1ef947e1071260422ed97d708',
inputs: [
{
$id: Now.ID['ticket_input_desc'],
name: 'short_description',
type: 'string',
value: t.input['user query'],
},
{
$id: Now.ID['ticket_input_priority'],
name: 'priority',
type: 'string',
value: t.input['priority'],
},
],
outputs: [
{ $id: Now.ID['ticket_output_number'], name: 'ticket_number', type: 'string' },
{ $id: Now.ID['ticket_output_status'], name: '__action_status__', type: 'string' },
],
depends: [fetchUserDetails],
})
// ────────────────────────────────────────────────────────
// 8. Skill — reference another NowAssist skill as a tool
// ────────────────────────────────────────────────────────
t.Skill('KnowledgeSearch', {
$id: Now.ID['tool_knowledge_skill'],
skillId: '739b26e553e5e21016f7ddeeff7b1237',
inputs: [
{
$id: Now.ID['knowledge_input_query'],
definitionAttributeId: 'aabb0000cccc1111',
value: t.input['user query'],
},
],
outputs: {
provider: { definitionAttributeId: 'prov_attr_id_001' },
response: { definitionAttributeId: 'resp_attr_id_002', truncate: true },
error: { definitionAttributeId: 'err_attr_id_003' },
errorCode: { definitionAttributeId: 'errc_attr_id_004' },
status: { definitionAttributeId: 'stat_attr_id_005' },
},
depends: [classifyIntent],
})
},
securityControls: {
userAccess: {
$id: Now.ID['acl-test'],
type: 'authenticated',
roles: ['admin'],
},
roleRestrictions: ['2831a114c611228501d4ea6c309d626d'],
},
deploymentSettings: {
uiAction: { table: 'incident' },
nowAssistPanel: {
enabled: true,
roles: ['now_assist_panel_user'],
},
},
},
{
providers: [
{
provider: 'Now LLM Service',
prompts: [
{
name: 'Resolve Query',
versions: [
{
$id: Now.ID['prompt_resolve_v1'],
model: 'llm_generic_small_v2',
temperature: 0.3,
prompt: (p) =>
`You are an IT support assistant.\n\nUser query: ${p.input['user query']}\nSearch results: ${p.tool.SearchKB.response}\nUser details: ${p.tool.FetchUserDetails.output}\n\nProvide a clear, actionable resolution.`,
promptState: 'draft',
},
],
},
],
},
],
}
)