npm.io
7.3.0 • Published 1 month ago

@exellix/ai-skills

Licence
exellix-license
Version
7.3.0
Deps
14
Size
586 kB
Vulns
0
Weekly
0

@exellix/ai-skills

Foundational skill execution layer for the exellix ecosystem: templates live in the Catalox native catalog ai-skills, execution uses @x12i/ai-gateway, and responses support FlexMD 2.0 structured text.

Env-Ready: Provider keys can be loaded from .env automatically. catalox is required at construction time (see Quick Start).

Catalox 6: Skill catalogs bootstrap via Mongo (ensureCataloxFromEnv / createCataloxFromEnvAsync from @exellix/catalox-ai-skills). Env: MONGO_URI or MONGODB_URI. See documenations/catalox-6-rollout.md.

Documentation

Topic Doc
Flex-MD vs gateway parsing, integration tests docs/FLEX_MD_AND_TESTING.md
Catalox integration & env notes docs/CATALOX_PEER_GUIDE.md · Environment (Mongo & Catalox 6)
Activix integration best practices .docs/activix-integration-best-practices-checklist.md
Per-package log level (AI_SKILLS_LOGS_LEVEL) Logging below · Logxer integration checklist · @x12i/logxer on npm
Gateway templates (v4), invoke() vs invokeChat(), templateRendering docs/GATEWAY_TEMPLATE_PROTOCOL_V4.md
This package: workingMemory, templateRenderOptions, client templateRendering docs/AI_SKILLS_GATEWAY_TEMPLATES.md
Upgrading to @x12i/ai-gateway 10.x (billing, required fields, removed behaviors) docs/UPGRADING_AI_GATEWAY_10.md
Invoke execution metadata (provider, modelUsed, effectiveModelConfig, …) docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md
Wire model on runSkill() (required; no alias / no gateway default) docs/MODEL_INVOKE_CONTRACT.md
Host invoke contract (reasoningEffort, no caller maxTokens) Host responsibilities · MODEL_INVOKE_CONTRACT.md
OpenRouter vs direct routing (PREFER_OPENROUTER) — not implemented here docs/PREFER_OPENROUTER_ROUTING.md
Graph execution context (graphId, nodeId, identity mapping) docs/GRAPH_EXECUTION_SUPPORT.md
Skills manager: preflight, FuncX, authoring (not in this package) docs/SKILLS_MANAGER_CLIENT_GUIDE.md
External follow-ups (Activix persistence vs gateway envelope) docs/AI_GATEWAY_FEATURE_REQUESTS.md
Gateway server tools (OpenRouter pass-through) docs/GATEWAY_SERVER_TOOLS.md · OPENROUTER_SERVER_TOOLS_WITH_AI_SKILLS.md · upstream CR for gateway

Features

  • Unified Skill Runner: Execute skills via metadata, not hardcoded handlers
  • FlexMD 2.0 Output: Always returns structured-text format with parsed payloads
  • Catalox-native templates: Instruction and prompt bodies are read from the Firestore native catalog ai-skills via @x12i/catalox; the gateway receives inline template text (not nx-content registry keys for skills)
  • Catalox provisioning: npm run catalox:provision-ai-skills upserts catalog metadata + Optimixer fields from AI_SKILLS_CATALOG_ITEMS (preserves template bodies already stored in Catalox)
  • Presentation API: Load pretty markdown for editors, save with lossless storage normalization (getSkillTemplatesForPresentation, updateSkillTemplatesFromPresentation, getSkillTemplateInputs); see Skill templates (Catalox)
  • Catalog audit fields (storage only): Catalox rows may store optional audit template text for editors; this package only executes runSkill (no built-in audit pass).
  • Gateway integration: @x12i/ai-gateway for LLM calls only; skill templates are never loaded from nx-content / GitHub — Catalox inline strings on invoke() only
  • Activity tracking: With enableActivityTracking, the gateway persists activities via @x12i/activix; this SDK enriches rows with cost / costStatus and contract-shaped outer.output.parsed when applicable (see Cost and output contract)
  • Model Configuration: Per-request model selection and sampling parameters (temperature, topP, reasoningEffort); completion token budget is Optimixer-owned (see Host responsibilities)
  • Full template pipeline (default): Uses gateway.invoke() with inline bodies; the gateway message builder renders tokens (downstream stack; e.g. Rendrix). runSkill populates workingMemory.input and can pass templateRenderOptions, templateTokens, or client-level templateRendering (see docs/AI_SKILLS_GATEWAY_TEMPLATES.md).
  • Execution engine catalog: Optional aiEngineId echoes the Catalox ai-engines row (ai-gateway only; provision with npm run catalox:provision-ai-engines) for discovery and identity — see Execution engines (aiEngineId).
  • Execution-only scope: Preflight / authoring / FuncX invoke review live in skills manager clients — see docs/SKILLS_MANAGER_CLIENT_GUIDE.md.

Installation

npm install @exellix/ai-skills

This package depends on @x12i/ai-gateway, @x12i/catalox, @x12i/logxer, @x12i/env, @x12i/rendrix, and @exellix/catalox-ai-skills (Mongo Catalox bootstrap). @x12i/catalox ≥ 6.0 expects Node 20+.

@x12i/ai-gateway ≥ 11.0.0 (openrouter-runtime 1.0.4, @x12i/ai-tools 3.3.5) (see package.json) with @x12i/activix ^9.0 and @x12i/ai-profiles ^3.4 is required: every invoke needs an explicit wire model and maxTokens (this package supplies maxTokens via @x12i/optimixer before gateway.invoke()). Invoke metadata includes routing fields (≥9.1.1), rejection metadata on failures (≥9.1.2), normalized billing (costUsd, cost, costStatus: priced | unpriced, optional costBreakdown) on success (≥9.3; ai-tools catalog pricing in gateway ≥10.1), and optional OpenRouter server-tool metadata (≥10.4.1). This package does not use @x12i/ai-tools directly — cost is resolved by the gateway. See docs/UPGRADING_AI_GATEWAY_10.md and docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md. Every runSkill call must include agentId, jobTypeId, and taskTypeId (Activix linkage — no package defaults).

CI: npm run test:ci runs npm run build and npm run test:unit (no live Firestore or real LLM). Full npm test still runs test:integration, which may append live catalog tests when env enables them.

Custom admin flows that call Catalox with createCatalog, bindCatalogToApp, or similar before an app binding exists should set superAdmin: true on CataloxContext (see defaultAiSkillsCataloxContext overrides and docs/CATALOX_PEER_GUIDE.md).

Publishing: the npm tarball is limited to dist/, README.md, and erc-manifest.json via the files field in package.json (avoids shipping tests, logs, or local scratch files).

Environment variables (Mongo & Catalox 6)

createCataloxFromEnvAsync() / ensureCataloxFromEnv() from @exellix/catalox-ai-skills delegate to @x12i/catalox/mongo.

Variable Notes
MONGO_URI or MONGODB_URI Required — Mongo connection string for Catalox catalogs
MONGO_DB_NAME / MONGODB_DB Optional database name (default catalox)

Provider keys for the default gateway (OPENAI_API_KEY, GROK_API_KEY, OPEN_ROUTER_KEY / OPENROUTER_API_KEY) are unchanged — see Troubleshooting below.

Quick Start

0) Prerequisites
  1. Node.js 20+ — matches @x12i/catalox engine requirements.
  2. Mongo Catalox — set MONGO_URI or MONGODB_URI (see above). Use await createCataloxFromEnvAsync() or pass your own Catalox from createCataloxFromMongoEnv.
  3. Provision the catalog (creates/updates Catalox app bindings, descriptors, and skill rows):
npm run catalox:provision-ai-skills
npm run catalox:provision-ai-engines
npm run catalox:verify
  1. At least one LLM provider key (OPENAI_API_KEY, GROK_API_KEY, or OPEN_ROUTER_KEY / OPENROUTER_API_KEY) for @x12i/ai-gateway.
1) Create the client
import { ExellixSkillsClient, RunSkillRequest, RunSkillResponse, createCataloxFromEnv } from "@exellix/ai-skills";

const catalox = createCataloxFromEnv();

const skills = new ExellixSkillsClient({
  catalox,
  enableActivityTracking: true,
});

You can also construct Catalox yourself (createCatalox from @x12i/catalox or @x12i/catalox/embedder with your firestore / firebaseApp) and pass options.catalox. An external gateway is optional; catalox is always required.

2) Run a skill
// Using the RunSkillRequest interface
const runSkillRequest: RunSkillRequest = {
  skillKey: "skills/professional-answer",
  input: "Question: What is the best way to migrate from X to Y?",
  variables: { orgName: "ACME", audience: "security team" },
  jobId: "job-123",
  taskId: "task-456",
  agentId: "agent-abc",
  jobTypeId: "your-job-type-id",
  taskTypeId: "your-task-type-id",
};

const res: RunSkillResponse = await skills.runSkill(runSkillRequest);

// Access FlexMD payloads
console.log(res.flexMd.payloads.shortAnswer);
console.log(res.flexMd.payloads.fullAnswer);
2b) Execution engines (aiEngineId)

runSkill always invokes @x12i/ai-gateway with inline Catalox template text; Rendrix runs inside the gateway. Optional aiEngineId must be omitted, blank, or ai-gateway — it is merged into gateway.invoke identity and matches the single row in the Catalox ai-engines catalog (provisioned for listing / UIs). Unknown values throw at resolve time.

2a) Run a skill with model configuration
// Override model and generation parameters per-request
const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Question: What is the best way to migrate from X to Y?",
  variables: { orgName: "ACME" },
  jobId: "job-123",
  taskId: "task-456",
  agentId: "agent-abc",
  jobTypeId: "your-job-type-id",
  taskTypeId: "your-task-type-id",
  modelConfig: {
    model: "gpt-4-turbo",
    temperature: 0.7,
    topP: 0.9,
    reasoningEffort: "medium",
  }
});

Host responsibilities (orchestrator MAIN)

Applies to @exellix/ai-tasks 8.4+ and other graph orchestrators calling runSkill() for the MAIN skill phase.

Send Do not send
Wire model / modelId (required) maxTokens, max_completion_tokens, max_output_tokens on modelConfig
Optional temperature, topP, reasoningEffort, frequencyPenalty, presencePenalty, retry, timeoutMs Profile / plan model names — resolve in @exellix/ai-tasks only
Optional serverTools, openrouter.apiMode (top-level or on modelConfig) Raw OpenRouter tool payloads, provider secrets
Optional provider Expectation that ai-skills picks a default model

Token budget: owned by @x12i/optimixer from Catalox catalog + prompts. After invoke, read metadata.usage.maxTokensRequested (and trace usage.maxTokensRequested when trace mode is on).

reasoningEffort: optional on modelConfig. Request value overrides Catalox optimixerReasoningEffort for that skill key; omit to keep catalog-only behavior. Resolution order: request → catalog → "not-applicable".

Full rules: docs/MODEL_INVOKE_CONTRACT.md · observability follow-ups: archived ai-skills-llm-observability.md.

2b) Optional: parser / template overrides

Per skill run you can pass templateRenderOptions (merged on gateway defaults) and templateTokens (highest-priority overlay). On the client, templateRendering sets defaults for every invoke. Details and examples: docs/AI_SKILLS_GATEWAY_TEMPLATES.md.

2c) Optional: identity propagation

identity is per-request runtime context from your application (trace ids, tenant, graph/node ids, etc.). It is not the SDK client’s constructor options, not env/config for this package, and not “package identity” from package.json—unless you explicitly copy such values into this object when you build the request.

identity vs runContext: The gateway invoke() API uses the property name identity (see @x12i/ai-gateway). Activix stores that same envelope on activity documents as BSON runContext. This SDK passes identity through to the gateway; you do not set runContext on runSkill inputs here. Responses expose the envelope as identity when the gateway returns it (and resolution may also consider metadata shapes that use runContext).

You can pass an identity object to runSkill() to propagate caller identity context downstream.

  • If you provide identity, the client forwards it as-is to the gateway.
  • The client will add identity.skillId from the executing request only if it is missing.
  • The RunSkillResponse includes identity only if downstream returns one. If downstream does not provide identity, it will be omitted (no fallback).
const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Question: ...",
  jobId: "job-123",
  taskId: "task-456",
  agentId: "agent-abc",
  jobTypeId: "your-job-type-id",
  taskTypeId: "your-task-type-id",
  skillId: "node-q0",
  identity: {
    traceId: "trace-123",
    userId: "user-456"
    // skillId is optional here; if omitted, it will be set from `skillId` above
  }
});

// Only present when downstream returned it:
console.log(res.identity);

// Token usage + cost (from gateway metadata); always on metadata.usage for every run.
// costUsd when priced; costStatus when usage exists but no price ("unpriced") or async pricing ("deferred").
console.log(res.metadata.usage, res.metadata.costUsd, res.metadata.costStatus, res.metadata.modelUsed);

// When `identity` is returned, the same snapshot is nested under `identity.aiSkillsLlm` for Activix-style
// chaining—forward the whole `identity` to the next `runSkill` so pipelines retain billing context.
console.log(res.identity?.aiSkillsLlm);
3) Model wire id, Optimixer budget, and trace visibility

Wire model (mandatory): Every runSkill() must include modelConfig.model or modelConfig.modelId as the concrete provider wire id resolved upstream (@exellix/ai-tasks / resolveInvocationPlan). Missing model is rejected before invoke; non-wire profile input is handled by @x12i/ai-gateway / @x12i/ai-tools, not duplicated here. No gateway default model in this package. See docs/MODEL_INVOKE_CONTRACT.md.

Completion token budget (Optimixer-owned): Caller maxTokens / max_completion_tokens / max_output_tokens are stripped (anomaly log). Budget is predicted by @x12i/optimixer from Catalox catalog fields and resolved reasoningEffort, then merged into modelConfig before gateway.invoke().

Max-token retry (MAIN hop): When the provider returns a limit finishReason (e.g. length) and Optimixer completeAiMaxTokensPrediction returns retryPrediction, this package re-invokes the gateway once with Optimixer’s bumped recommendedMaxTokens (caller ceilings still apply via existing merge). Up to one retry; observability includes metadata.optimixerRetryCount and log code OPTIMIXER_MAX_TOKENS_RETRY. No local token multipliers or re-predict bypass.

Other modelConfig fields (temperature, topP, reasoningEffort, provider, serverTools, openrouter, …) are forwarded when set.

3b) Gateway server tools (OpenRouter pass-through)

@exellix/ai-skills forwards OpenRouter server-tool intent to @x12i/ai-gateway (≥ 10.4.4). This package does not execute OpenRouter tools locally and does not depend on @x12i/openrouter-runtime.

  • Set serverTools on the request, on modelConfig, in Catalox data.runtime.serverTools, or via client defaultServerTools (merge order: client → catalog → request → modelConfig).
  • Tool usage, citations, generated images, and patch proposals appear on RunSkillResponse.metadata (not in FlexMD unless the skill template asks for them).
  • No tools are enabled by default; existing skills behave unchanged unless you opt in.

See docs/GATEWAY_SERVER_TOOLS.md · docs/OPENROUTER_SERVER_TOOLS_WITH_AI_SKILLS.md.

const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "What changed in the vendor docs?",
  jobId: "job-1",
  taskId: "task-1",
  agentId: "agent-1",
  jobTypeId: "research",
  taskTypeId: "answer",
  modelConfig: {
    provider: "openrouter",
    model: "openai/gpt-5.2",
    serverTools: {
      webSearch: { mode: "required", engine: "auto", maxResults: 5 },
      webFetch: { mode: "allowed", maxContentTokens: 50000 },
    },
  },
});

console.log(res.flexMd.payloads);
console.log(res.metadata.citations);
console.log(res.metadata.serverTools);
What you need Where it appears
What was sent toward invoke (merged modelConfig, timeoutMs) Trace-only: debugTrace.invokeRequest when executionMode: "trace" or diagnostics.includeDebugTrace
Effective completion cap metadata.usage.maxTokensRequested · trace debugTrace.usage.maxTokensRequested
Effective reasoningEffort Trace debugTrace.invokeRequest.modelConfig.reasoningEffort (request override or catalog default)
What ran (model id, token counts) debugTrace.modelUsed, debugTrace.usage, routing ids
const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Question: ...",
  jobId: "job-1",
  taskId: "task-1",
  agentId: "agent-abc",
  jobTypeId: "your-job-type-id",
  taskTypeId: "your-task-type-id",
  modelConfig: {
    model: "gpt-4-turbo",
    temperature: 0.5,
    reasoningEffort: "high",
  },
  executionMode: "trace",
});

console.log(res.debugTrace?.invokeRequest?.modelConfig?.reasoningEffort);
console.log(res.metadata.usage?.maxTokensRequested);
console.log(res.debugTrace?.usage.maxTokensRequested);

On gateway failures with trace mode enabled, SkillExecutionTraceError carries diagnostics.trace with the same shapes where available; metadata attached on the thrown error is merged when present (see metadataFromInvokeError export).

Cost and output contract (Run Analysis)

Studio / graph Run Analysis expects activities and responses to explain billing and structured output. This package normalizes both on every successful runSkill and best-effort patches the persisted Activix row when metadata.activityId is present.

Cost reporting (metadata.costUsd / metadata.costStatus)

Gateway invoke() metadata (≥ 9.3, ai-tools catalog in gateway ≥ 10.1) uses costUsd / cost when priced and costStatus: "priced" | "unpriced". Optional costBreakdown (prompt/completion USD) is forwarded when the gateway catalog pricing includes it. This SDK never calculates cost locally — it normalizes gateway metadata only. Priced runs expose costUsd only (gateway "priced" status is not echoed). When usage exists but no USD total is known, costStatus: "unpriced" is set (gateway explicit flag or SDK fallback).

Gateway / router situation Gateway metadata On RunSkillResponse.metadata Activix record (gateway logSuccess + SDK patch)
Priced (router or ai-tools catalog) costUsd, cost, costStatus: "priced", optional costBreakdown costUsd, optional costBreakdown (no costStatus) Root cost / costUsd / costStatus: "priced", outer.cost.usd; prefer these over outer.metadata for billing
Usage, no price costStatus: "unpriced" costStatus: "unpriced" Root costStatus: "unpriced" + token usage on root metadata / response.metadata
Legacy async pricing costStatus: "deferred" (older gateways) costStatus: "deferred" same

Gateway logSuccess writes billing on the activity row during invoke() when enableActivityTracking is on (default). patchActivixActivityAfterSkill then merges the same billing slice plus outputContract parsed fields so Run Analysis and Activix queries stay aligned with RunSkillResponse.metadata.

metadata.usage is always populated (zeros when the gateway omits token fields). The same billing slice is mirrored on identity.aiSkillsLlm when the gateway returns identity, and on debugTrace in trace mode.

Output contract (outputContract)

outputContract is per invoke, not a global list for all skills. Pass the field names your graph node or skill expects on parsed / outer.output.parsed — typically from graph inputs.outputContract (via @exellix/ai-tasks / graph-engine).

Supported shapes: string[], { fields: string[] }, { keys: string[] }, or { required: string[] }.

After FlexMD / gateway parsing (and local parseFlexMd fallback), the SDK fills missing contract keys from:

  1. flexMd.payloads
  2. Markdown section headings (### Short AnswershortAnswer)

Examples (illustrative — use the contract for that skill/node):

// professional-answer–style skill
await skills.runSkill({
  skillKey: "skills/professional-answer",
  outputContract: ["shortAnswer", "fullAnswer", "assumptions", "unknowns", "evidence"],
  // ...jobId, taskId, agentId, jobTypeId, taskTypeId, input, ...
});

// professional-decision–style skill
await skills.runSkill({
  skillKey: "skills/professional-decision",
  outputContract: ["decision", "score", "rationale", "risks"],
  // ...
});

This package does not auto-map skillKey to a catalog contract; upstream must pass outputContract when structured fields are required. Catalog payload names are documented in docs/metadata.md (requiredPayloads per skill).

Exports (for orchestrators and tests): normalizeSkillBillingFromGatewayMetadata, enrichParsedForOutputContract, patchActivixActivityAfterSkill, types SkillCostStatus, OutputContract.

Direct functions (re-consider)

Typed wrappers on ExellixSkillsClient that call revision skills with nested templateTokens. Shared runtime: ReConsiderationRuntime (jobId, taskId, agentId, jobTypeId, taskTypeId, modelConfig). Object fields (context, providedContext, etc.) are stringified via stringifyForPrompt so they never become [object Object]. Callers own merge/apply of returned fragments.

Method Skill Result
fillReConsiderationGap re-consider.fill-gap { gapOutput }
reviewReConsiderationResponse re-consider.review { reviewStatus, reviewOutput }
rewriteReConsiderationScope re-consider.rewrite-scope { correctedScope }
rewriteReConsiderationFull re-consider.rewrite-full { correctedAnswer }
calibrateReConsiderationDecision re-consider.calibrate-decision { calibratedOutput }
const result = await skills.fillReConsiderationGap(
  {
    input: "What are the risks?",
    originalInstructions: "Answer with evidence.",
    originalResponse: priorAnswer,
    gapExplained: "Missing evidence bullets for Section B.",
    fragmentName: "Section B",
    context: { assetId: "db-1" },
  },
  {
    jobId: "job-1",
    taskId: "task-1",
    agentId: "agent-1",
    jobTypeId: "job-type",
    taskTypeId: "task-type",
    modelConfig: { model: "gpt-4o-mini" },
  }
);
// result.gapOutput — insert or replace; do not expect a merged full response

Token roots (examples): taskVariables.gap.* / jobMemory.gap.*, taskVariables.review.* / jobMemory.review.*, taskVariables.scope.* / jobMemory.scope.*, taskVariables.full.* / jobMemory.full.*, taskVariables.calibration.* / jobMemory.calibration.*, plus jobMemory.providedContext and {{input}}. Chain prior outputs via optional gapResults / reviewResults / rewriteResults / calibrationResults.

Also exported as standalone functions (and request builders) from @exellix/ai-skills.

Built-in skills (catalog)

Authoritative rows are defined in code as AI_SKILLS_CATALOG_ITEMS and written to Catalox by npm run catalox:provision-ai-skills. Typical runnable keys:

  • skills/professional-answer — structured professional answer (FlexMD payloads)
  • skills/re-consider.fill-gap — fill a known gap (usageHints.skillMode: "revision")
  • skills/re-consider.review — review response → reviewStatus + reviewOutput
  • skills/re-consider.rewrite-scope — rewrite one selected scope → correctedScope
  • skills/re-consider.rewrite-full — full rewrite → correctedAnswer
  • skills/re-consider.calibrate-decision — calibrate decision/priority/ranking → calibratedOutput
  • Additional catalog rows / forks: use listCatalogSkills(), listPublishedSkills(), or Catalox listCatalogItems for the live list.

runSkill takes skillKey like skills/professional-answer. Bodies are not resolved as nx-content registry keys for execution; they are loaded from Catalox and sent to the gateway as inline template strings.

Skill templates (Catalox)

Provisioning (metadata + Optimixer → Catalox)

npm run catalox:provision-ai-skills upserts rows from AI_SKILLS_CATALOG_ITEMS (title, description, Optimixer fields, status). It preserves existing instructionsText / promptText already in Catalox — template bodies are authored in Catalox only (via updateSkillTemplatesFromPresentation, upsertSkillCatalogItem, or your admin tooling).

npm run catalox:provision-ai-skills
npm run catalox:verify

Runnable skills need both bodies in Catalox and status draft or published (computed from body completeness + catalogReleaseStatus on provision).

Catalog manager API (skills studio / admin)

For apps that manage the catalog without running LLMs, use createAiSkillsCatalogApi({ catalox }) — full CRUD + fork. See docs/AI_SKILLS_CATALOG_MANAGER.md.

Function Purpose
listSkills / listSubSkills List base and sub-skill rows
getSkill / getSkillContent Read metadata and template markdown
createSkill / updateSkill Create and patch rows
forkSkill Sub-skill fork with optional overrides
deleteSkill Hard-delete sub-skills; soft-delete base skills
publishSkill / unpublishSkill Release status shortcuts

Lower-level ExellixSkillsClient methods below remain available when you already use the execution client.

Presentation layer (read / edit)

Low-level helpers live under @exellix/ai-skills exports from ./catalox (e.g. normalizeForStorage, toPresentationMarkdown, extractTemplateTokensFromTexts).

On ExellixSkillsClient:

Method Purpose
getSkillTemplatesForPresentation(skillKey, { includeAudit? }) Catalox read → markdown formatted for editors
updateSkillTemplatesFromPresentation(skillKey, patch, options?) Editor markdown → lossless storage normalization → Catalox upsert (requires write Catalox context)
getSkillTemplateInputs(skillKey) Union of {{token}} placeholders from raw stored bodies; treats input as the primary payload token
resolveRawTemplate(skillKey, section, { role, strategyKey? }) Raw Catalox bodies for PRE synthesis / orchestration / audit roles (used by @exellix/ai-tasks)
resolveSynthesisContextStrategyKey(skillKey) Default Catalox synthesisContext template key from supportedCapabilities.synthesisContext (graphenix 2.1.0 — plan wire must not carry unitParams.synthesisInputStrategy)
listCatalogSkills(options?) Lists every catalog row (all status values); forwards Catalox query options (e.g. limit)
upsertSkillCatalogItem(input, options?) Create/update full row (metadata + optional markdown); optional ifNotExists for strict create
softDeleteSkillCatalogItem(skillKey) Clears bodies and audit fields and sets planned (no hard delete)

Write access: updates use batchUpsertNativeCatalogItems; use a Catalox context with permission to write the catalog (e.g. provision-style god mode or an app binding with write). This package does not create credentials.

Skill key usage
await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Your question here...",
});

Catalox item ids are the short key (e.g. professional-answer); the client accepts either skills/professional-answer or the short id where helpers normalize.

Troubleshooting
options.catalox is required

Pass catalox into new ExellixSkillsClient({ catalox, ... }). Use createCataloxFromEnv() from this package, createCataloxFromEnv() from @x12i/catalox/firebase, or createCatalox({ firestore, firebaseApp }) from @x12i/catalox / @x12i/catalox/embedder.

Skill is planned or “incomplete templates”

Run npm run catalox:provision-ai-skills (or write bodies via updateSkillTemplatesFromPresentation) so instructionsText and promptText are non-empty and status is draft or published.

Provider / env validation

At least one of OPENAI_API_KEY, GROK_API_KEY, OPEN_ROUTER_KEY, OPENROUTER_API_KEY is required when the client builds the default gateway. Firebase / Catalox variables are summarized above.

Logging (AiSkills / @x12i/logxer)

This package uses @x12i/logxer with three stable identifiers:

Field Value Purpose
packageName AiSkills package column in logs, shadow, Mongo
envPrefix AI_SKILLS Env vars and stack keys
debugNamespace ai-skills DEBUG= filter slug

Exported constants: AI_SKILLS_LOGXER_PACKAGE, AI_SKILLS_LOGXER_ENV_PREFIX, AI_SKILLS_LOGXER_DEBUG_NAMESPACE, AI_SKILLS_LOGXER_PACKAGE_CONFIG, createAiSkillsLogger (alias createAiSkillsLogxer).

Item Detail
Canonical env var AI_SKILLS_LOGS_LEVEL
Fallback AI_SKILLS_LOG_LEVEL is used only if AI_SKILLS_LOGS_LEVEL is unset
Default (both unset) warn — not silent; you will see warnings and errors
Silence this package Set AI_SKILLS_LOGS_LEVEL=off (or none / silent)
More detail info, debug, or verbose (case-insensitive; see @x12i/logxer docs)
Full payloads Set AI_SKILLS_VERBOSE=true and AI_SKILLS_LOGS_LEVEL=verbose — attaches sanitized gateway request/response on runSkill()
Bulk stack config LOGXER_PACKAGE_LEVELS=AI_SKILLS:debug,... (host calls applyPackageLogLevelsFromEnv() at startup)

Cross-cutting sinks (console, file, JSON, unified app config) are configured by the host app, not per this prefix — see the @x12i/logxer README.

Console output includes app: (host app from cwd package.json) and package: (AiSkills for lines from this library). Optional packageName on the client sets RuntimeIdentity.service only — it does not change the Logxer package field.

Per-path tiers:

Path info debug verbose + AI_SKILLS_VERBOSE
runSkill() One completion summary (duration, provider, model, tokens) Entry, Catalox fetch, invoke routing, FlexMD steps Sanitized gateway request/response
runSkill() One completion summary per invoke Gateway invoke, Optimixer merge Response metadata / trace when enabled
Catalog / registry discoverStructure, diagnose, runDiagnostics completion only List/probe/connection tests, client init/shutdown N/A

Failures on invoke and config validation emit at error / warnCode with correlation ids.

.env: with default autoLoadDotenv, the client loads .env from the current working directory using loadDotenv from @x12i/env.

# Threshold for this package
export AI_SKILLS_LOGS_LEVEL=info

# Optional: full payloads (still requires _LOGS_LEVEL=verbose to print)
export AI_SKILLS_VERBOSE=true

# Bulk stack config (loaded at startup)
export LOGXER_PACKAGE_LEVELS=AI_SKILLS:debug,AI_GATEWAY:warn

Programmatic stack (logxer ≥ 4.5):

import { type StackLoggingOptions } from "@x12i/logxer";
import { ExellixSkillsClient, AI_SKILLS_LOGXER_ENV_PREFIX, createCataloxFromEnv } from "@exellix/ai-skills";

const logging: StackLoggingOptions = {
  packageLevels: { [AI_SKILLS_LOGXER_ENV_PREFIX]: "debug" },
};

const skills = new ExellixSkillsClient({
  catalox: createCataloxFromEnv(),
  logging,
  verbose: true,
});

Filter Mongo / shadow logs with getJobLogs({ package: "AiSkills", ... }).

Testing (integration + templates)

This repo is ESM ("type": "module"). Use the npm scripts below (they run via tsx for .ts entrypoints).

Default test gate (npm test): npm run build then npm run test:integration (may append live catalog tests when env enables them). npm run test:ci runs npm run build and npm run test:unit (mocked gateway / no Firestore / no LLM).

npm test                       # build + integration harness (see run.ts)
npm run test:ci                # build + deterministic unit slice (CI-safe)
npm run test:unit              # same unit slice as test:ci (without build)
npm run test:integration       # full integration harness (run.ts)
npm run catalox:provision-ai-skills   # upsert catalog metadata + Optimixer fields (before live tests)
npm run catalox:provision-ai-engines  # upsert in-repo engine rows → native `ai-engines`
npm run catalox:verify                # validate live Catalox rows for runtime requirements
npm run live:professional-answer      # smoke: professional-answer via Catalox + default gateway LLM
npm run live:re-consider              # smoke: all five re-consider direct functions via Catalox + LLM
npm run test:real              # extra real LLM scenarios; needs .env + valid Firebase credentials
Live tests (opt-in, test/run.ts)

Some cases use real Catalox (Mongo-backed catalog) and the same integration slice can invoke real LLM calls when provider keys are set. They are off by default so CI does not require a provisioned project.

Set AI_SKILLS_LIVE_TESTS=1 (or true / yes / on) before npm run test:integration to append the live cases registered in test/run.ts:

  • Catalox ai-skills list — read-only catalog list sanity check.
  • Catalox ai-engines list — verifies npm run catalox:provision-ai-engines wrote the ai-gateway row (read-only).
  • Re-consider direct functions — real LLM calls for fillReConsiderationGap, reviewReConsiderationResponse, rewriteReConsiderationScope, rewriteReConsiderationFull, calibrateReConsiderationDecision (provision + sync templates first).

Unit mocks for re-consider live only under test/ (CI wiring via fake gateway). Production code and npm run live:re-consider use real Catalox + real LLM only — no mock fallbacks.

Why not in @x12i/catalox: only this package’s test harness reads AI_SKILLS_LIVE_TESTS; Catalox does not gate downstream CI.

Optionally run npm run test:real before a major release (broader LLM + client scenarios; needs Mongo + provider keys in .env).

Flex-MD log noise (extractJsonFromFlexMd / require(...) is not a function): that path lives in @x12i/ai-gateway, not in this package’s parseFlexMd. See docs/FLEX_MD_AND_TESTING.md for the split of responsibilities and acceptance notes.

Model Configuration

You can control which model is used and how it generates responses on a per-request basis using the modelConfig field.

Model Configuration Options
interface ModelConfig {
  /** Model identifier (e.g., "gpt-4-turbo", "claude-3-opus", "gpt-3.5-turbo") */
  model?: string;
  
  /** Model ID (alternative to model name, for provider-specific model IDs) */
  modelId?: string;
  
  /** Provider name (e.g., "openai", "anthropic") */
  provider?: string;
  
  /** Temperature for generation (0.0 to 2.0) - controls randomness */
  temperature?: number;
  
  /** @deprecated Caller maxTokens is stripped — Optimixer sets completion budget from Catalox catalog. */
  maxTokens?: never;
  
  /** Top-p (nucleus) sampling parameter (0.0 to 1.0) */
  topP?: number;

  /** Optimixer reasoning depth; overrides Catalox catalog default when set. */
  reasoningEffort?: "not-applicable" | "none" | "low" | "medium" | "high";
  
  /** Frequency penalty (-2.0 to 2.0) */
  frequencyPenalty?: number;
  
  /** Presence penalty (-2.0 to 2.0) */
  presencePenalty?: number;
  
  /** Stop sequences (array of strings) */
  stop?: string[];
  
  /** Additional provider-specific parameters */
  [key: string]: any;
}
Usage Examples
Basic Model Selection
const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Your question here",
  modelConfig: {
    model: "gpt-4-turbo"
  }
});
Full Model Configuration
const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Your question here",
  modelConfig: {
    model: "gpt-4-turbo",
    temperature: 0.7,
    topP: 0.9,
    reasoningEffort: "medium",
    frequencyPenalty: 0.5
  }
});
Per-node reasoning depth
const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Your question here",
  jobId: "job-1",
  taskId: "task-1",
  agentId: "agent-abc",
  jobTypeId: "your-job-type-id",
  taskTypeId: "your-task-type-id",
  modelConfig: {
    model: "gpt-4-turbo",
    reasoningEffort: "high", // overrides Catalox optimixerReasoningEffort for this invoke
  },
});
Provider Override
const res = await skills.runSkill({
  skillKey: "skills/professional-answer",
  input: "Your question here",
  modelConfig: {
    provider: "anthropic",
    model: "claude-3-opus",
    temperature: 0.3
  }
});
Notes
  • modelConfig.model / modelId are required (concrete wire id). Missing or alias-shaped values throw before gateway.invoke(). See docs/MODEL_INVOKE_CONTRACT.md.
  • maxTokens on the request are stripped; Optimixer sets the cap from Catalox catalog fields and resolved reasoningEffort.
  • reasoningEffort on the request overrides the Catalox catalog default for that skill key.
  • This package does not merge a global default modelConfig at construction time; only templateRendering has client-wide defaults for template rendering.
  • The gateway and provider enforce valid ranges (e.g. temperature).
  • OpenRouter routing policy (PREFER_OPENROUTER) is not applied in this package. Orchestrators (@exellix/ai-tasks, graph-engine) resolve the plan and set the wire model on modelConfig before runSkill(). See docs/PREFER_OPENROUTER_ROUTING.md.

API Reference

ExellixSkillsClient
Constructor options (essential)
import type { Catalox } from "@x12i/catalox";
import type { TemplateRenderOptions } from "@x12i/ai-gateway";

interface ExellixSkillsClientOptions {
  /** Required. Skill bodies are read from the native `ai-skills` catalog. */
  catalox: Catalox;
  /** Optional; defaults to catalog app id `ai-skills`. */
  cataloxAppId?: string;

  packageName?: string;
  enableActivityTracking?: boolean;
  /** Optional external gateway; skill templates still load from `catalox`. */
  gateway?: AIGateway;

  templateRendering?: TemplateRenderOptions;
  autoLoadDotenv?: boolean;
  testMode?: boolean;
  disableActivityTrackingInTests?: boolean;
}

The built-in AIGateway has no nx-content / GitHub skill registry; GITHUB_* is not required for ExellixSkillsClient initialization.

Methods
  • runSkill<T>(input: RunSkillRequest): Promise<RunSkillResponse<T>> — loads templates from Catalox, invokes gateway with inline bodies. Optional outputContract enriches parsed and the Activix activity row (see Cost and output contract).
  • getSkillTemplatesForPresentation(skillKey, opts?) — editor-oriented markdown + metadata.
  • updateSkillTemplatesFromPresentation(skillKey, patch, opts?) — save edited markdown (lossless storage normalization); requires Catalox write context.
  • getSkillTemplateInputs(skillKey){{token}} discovery from stored bodies.
  • listPublishedSkills(options?) — Catalox rows with status === "published".
  • listCatalogSkills(options?) — all catalog rows (any status); optional Catalox list query options.
  • upsertSkillCatalogItem(input, options?) — create/update catalog metadata and template bodies; options.ifNotExists enforces create-only.
  • softDeleteSkillCatalogItem(skillKey) — clears template and audit text and sets planned.

Template bodies are loaded only through runSkill (and the same Catalox fetch used there): pass skillKey; there is no separate “peek at one section string” API.

Diagnostic methods (Catalox catalog)
  • testCatalogConnection(), discoverCatalogStructure(), diagnoseSkillInCatalog(), listAvailableSkills(), runCatalogDiagnostics() — Catalox ai-skills catalog reachability and discovery.

For {{token}} discovery on stored bodies without invoking the LLM, use getSkillTemplateInputs(skillKey) (Catalox read + parse).

FlexMD 2.0 Format

Primary skills are designed around structured markdown (headings and/or Flex-MD style markers). A common explicit shape is:

[[professional-answer]]

@payload:shortAnswer
Brief answer here...

@payload:fullAnswer
Detailed answer here...

@payload:assumptions
List of assumptions...

The gateway normalizes responses with its own Flex-MD extraction. When it does not return parsed fields, this SDK uses parseFlexMd (src/utils/flex-md-parser.ts), which prefers the flex-md package via ESM import() and falls back to parsing [[frame:...]] + @payload: blocks.

When you pass outputContract, the SDK also maps markdown ### Section headings to camelCase keys (e.g. ### Full AnswerfullAnswer) so professional-answer templates that use headings still populate parsed even without @payload: markers. See Cost and output contract.

Details and caveats: docs/FLEX_MD_AND_TESTING.md.

Requirements

  • Node.js 20+ (see package.json engines; aligns with @x12i/catalox)
  • @x12i/catalox ≥ 4 and Firebase access for the ai-skills native catalog (see createCataloxFromEnv and Environment (Firebase & Catalox v4))
  • @x12i/ai-gateway ^10.4.4 (see package.json; shares @x12i/activix ^9.0 with this package)
  • @x12i/ai-profiles ^3.0.0 (profile/choice → wire model resolution)
  • @x12i/activix ^8.6.0 (direct dependency for activity-row enrichment types/patches)
  • At least one LLM provider key when using the default constructed gateway

Downstream orchestrators: @exellix/ai-tasks ≥ 8.7.0 depends on this package (≥ 6.4.0) and @exellix/xynthesis ≥ 4.5.0 for the full PRE/MAIN/POST stack — see docs/UPGRADING_AI_GATEWAY_10.md.

Task and graph orchestration live in sibling packages, not in @exellix/ai-skills:

  • @exellix/ai-tasks — task orchestration and task-level runtime wiring (≥ 8.7.0 pins @exellix/ai-skills ≥ 6.4.0 + @exellix/xynthesis ≥ 4.5.0; gateway 10.x billing and invoke rules apply to MAIN via this package)
  • @exellix/graph-engine — graph execution and graph-level runtime wiring; forward each node’s inputs.outputContract on runSkill when Run Analysis or downstream validation require structured parsed fields
  • @exellix/exellix-runtime — root runtime composition (for example loading composed runtimeObjects for debug tooling)

Older scopes such as @woroces/*, worox, worex, and graph packages named worox-graph / worex-graphs are not used here; there are no intentional aliases or compatibility shims for those names in this repository.

License

ISC

Keywords