Skip to main content

Bot Skills — Technical Documentation

Bot Skills add deterministic, multi-step conversation flows to the ICE (Intelligent Conversation Engine) pipeline. They intercept message processing after classification/escalation but before the freeform brain runs.


Architecture​

Customer Message
│
▼
┌──────────┐
│ Classify │ ← Intent + sentiment + urgency
└──────────┘
│
▼
┌──────────┐
│ Escalate? │ ← Check escalation rules
└──────────┘
│ (not escalated)
▼
┌──────────┐
│ Active │ ← Resume if thread has activeSkillState
│ Skill? │
└──────────┘
│ (no active skill)
▼
┌──────────┐
│ Match │ ← matchSkill(): intent OR keyword + conditions
│ Skill? │
└──────────┘
│ (no match)
▼
┌──────────┐
│ Brain │ ← Freeform AI: decide → generate → comply
└──────────┘

Database Models​

BotSkill​

ColumnTypeDescription
idString (cuid)Primary key
workspaceIdStringFK to Workspace
nameStringSkill name
descriptionString?Optional description
isEnabledBooleanDefault true
priorityIntLower = higher priority (default 0)
triggerIntentsString[]Array of intent types that trigger this skill
triggerKeywordsString[]Array of normalized keywords
conditionsJson?Array of SkillStepCondition objects
stepsJsonArray of BotSkillStep objects

Indexes: @@index([workspaceId, isEnabled])

BotSkillExecution​

ColumnTypeDescription
idString (cuid)Primary key
workspaceIdStringFK to Workspace
skillIdStringFK to BotSkill
threadIdStringFK to ConversationThread
statusStringin_progress | completed | abandoned | failed
currentStepIdStringCurrent step being executed
stepsSnapshotJsonFrozen copy of steps at execution start
collectedDataJsonKey-value data collected from customer
startedAtDateTimeExecution start time
completedAtDateTime?When skill finished
failedReasonString?Error description if failed

Indexes: @@index([workspaceId]), @@index([threadId, status])

ConversationThread Addition​

  • activeSkillState Json? — Stores BotSkillExecutionState for active skill

Shared Types​

Key Types (@omnisocial/shared)​

type BotSkillStepType = 'message' | 'collect_input' | 'condition' | 'tool_call' | 'transfer' | 'end';

interface BotSkillStep {
id: string;
type: BotSkillStepType;
name: string;
messages?: Record<string, string>; // locale map
inputPrompt?: Record<string, string>; // locale map
inputKey?: string;
inputValidation?: { type: 'text'|'email'|'phone'|'number'|'choice'; options?: string[]; regex?: string; errorMessage?: Record<string, string> };
branches?: Array<{ conditions: SkillStepCondition[]; nextStepId: string }>;
defaultNextStepId?: string;
toolName?: string;
toolInput?: Record<string, string>; // e.g. { query: "collectedData.orderNumber" }
toolResultKey?: string;
toolResultMessage?: Record<string, string>;
transferReason?: Record<string, string>;
transferPriority?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
nextStepId?: string | null;
}

interface SkillStepCondition {
field: string;
operator: 'eq' | 'neq' | 'contains' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'matches';
value: unknown;
}

interface BotSkillExecutionState {
executionId: string;
skillId: string;
currentStepId: string;
stepsSnapshot: BotSkillStep[];
collectedData: Record<string, unknown>;
}

Service Layer​

File: apps/api/src/services/bot-skill.service.ts

CRUD Functions​

FunctionDescription
listSkills(workspaceId)List all skills ordered by priority
getSkill(workspaceId, skillId)Get single skill
createSkill(workspaceId, input)Create skill (validates step refs, normalizes keywords)
updateSkill(workspaceId, skillId, input)Update skill (same validations)
deleteSkill(workspaceId, skillId)Delete skill
toggleSkill(workspaceId, skillId, enabled)Enable/disable skill

Matching​

matchSkill(workspaceId, classification, messageText):

  1. Load all enabled skills ordered by priority
  2. For each skill, check if intent matches OR normalized keyword is found in message
  3. If conditions are present, evaluate all structured conditions against classification context
  4. Return first match or null

Execution Engine​

startSkillExecution(workspaceId, threadId, skill):

  1. Snapshot the skill's steps into a new BotSkillExecution record
  2. Set activeSkillState on the thread
  3. Run runStepsUntilPause() — executes steps in a loop until hitting collect_input, transfer, or end
  4. Return the accumulated reply text

continueSkillExecution(workspaceId, threadId, customerMessage):

  1. Read activeSkillState from thread
  2. If current step is collect_input, validate the customer's message and store it
  3. Advance to next step and run runStepsUntilPause()
  4. Update execution state or clear it if completed

abandonSkillExecution(workspaceId, threadId):

  • Marks execution as abandoned and clears activeSkillState

Step Execution Loop​

runStepsUntilPause() iterates through steps with a safety limit of 20 iterations:

Step TypeBehavior
messageResolve locale message, add to reply, advance to nextStepId
collect_inputResolve locale prompt, add to reply, pause (return state)
conditionEvaluate branches against collectedData, jump to matching step
tool_callResolve inputs from collectedData, call tool executor, format result, advance
transferResolve reason, add to reply, return with action: 'transfer'
endResolve message, add to reply, return with action: 'end'

Keyword Normalization​

normalizeKeyword(keyword):

  • Lowercase + trim
  • Arabic alef normalization: أ, إ, آ → ا
  • Taa marbuta normalization: ة → ه

Language Detection​

Unicode script analysis — counts Arabic vs Latin characters. Falls back to en.

Locale Resolution​

resolveLocaleMessage(messages, language):

  1. Try detected language
  2. Fall back to en
  3. Fall back to first available key
  4. Return empty string if no messages

API Routes​

All routes are under /ice/ prefix on the existing ICE router.

MethodPathAuthDescription
GET/ice/skillsAny memberList all skills
GET/ice/skills/:idAny memberGet skill by ID
POST/ice/skillsOWNER/ADMINCreate new skill
PUT/ice/skills/:idOWNER/ADMINUpdate skill
DELETE/ice/skills/:idOWNER/ADMINDelete skill
PUT/ice/skills/:id/toggleOWNER/ADMINToggle enabled/disabled
GET/ice/skill-executionsAny memberList execution history

Pipeline Integration​

File: apps/api/src/workers/ice-process.worker.ts

Two insertion points in the message processing pipeline:

1. Skill Continuation (before classification)​

If thread.activeSkillState exists, the message is routed to continueSkillExecution(). The reply is delivered and processing returns early — no classification or brain execution occurs.

2. Skill Matching (after escalation, before brain)​

After escalation rules are checked, matchSkill() is called. If a skill matches, startSkillExecution() is called, the first reply is delivered, and processing returns early.


Frontend​

Components​

  • SkillsTab (agent-builder/SkillsTab.tsx) — Skill list with cards, toggle, delete, create/edit buttons
  • SkillEditorDialog (agent-builder/SkillEditorDialog.tsx) — Full dialog for creating/editing skills with step editor

API Client​

All methods on iceApi object in apps/web/src/api/ice.ts:

  • listSkills(), getSkill(id), createSkill(data), updateSkill(id, data), deleteSkill(id), toggleSkill(id, enabled), listSkillExecutions(params)

Testing​

  • Backend: apps/api/src/services/__tests__/bot-skill.service.test.ts — CRUD, keyword normalization, step reference validation, skill matching, execution engine
  • Frontend: apps/web/src/pages/conversation-engine/agent-builder/__tests__/SkillsTab.test.tsx — Rendering, toggle, delete, create/edit dialog integration

Design Decisions​

  1. Structured conditions over string eval — { field, operator, value } objects prevent code injection
  2. Locale maps over separate fields — Record<string, string> scales to any number of languages
  3. Step snapshots — Steps are frozen at execution start to prevent mid-conversation breakage if the skill is edited
  4. Structured toolInput — Record<string, string> mapping instead of template strings avoids injection
  5. Step ID validation — All nextStepId/branch references are validated on create/update to prevent dangling references
  6. Arabic keyword normalization — Alef variants and taa marbuta are normalized for consistent matching