@exellix/ai-skills
v7.2.2
Published
Foundational skill execution layer for exellix ecosystem using @x12i/ai-gateway with FlexMD 2.0 support and Catalox as the catalog store
Readme
@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/createCataloxFromEnvAsyncfrom@exellix/catalox-ai-skills). Env:MONGO_URIorMONGODB_URI. Seedocumenations/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-skillsvia@x12i/catalox; the gateway receives inline template text (not nx-content registry keys for skills) - Catalox provisioning:
npm run catalox:provision-ai-skillsupserts catalog metadata + Optimixer fields fromAI_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-gatewayfor LLM calls only; skill templates are never loaded from nx-content / GitHub — Catalox inline strings oninvoke()only - Activity tracking: With
enableActivityTracking, the gateway persists activities via@x12i/activix; this SDK enriches rows withcost/costStatusand contract-shapedouter.output.parsedwhen 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).runSkillpopulatesworkingMemory.inputand can passtemplateRenderOptions,templateTokens, or client-leveltemplateRendering(see docs/AI_SKILLS_GATEWAY_TEMPLATES.md). - Execution engine catalog: Optional
aiEngineIdechoes the Cataloxai-enginesrow (ai-gatewayonly; provision withnpm run catalox:provision-ai-engines) for discovery andidentity— 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-skillsThis 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
- Node.js 20+ — matches
@x12i/cataloxengine requirements. - Mongo Catalox — set
MONGO_URIorMONGODB_URI(see above). Useawait createCataloxFromEnvAsync()or pass your ownCataloxfromcreateCataloxFromMongoEnv. - 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- At least one LLM provider key (
OPENAI_API_KEY,GROK_API_KEY, orOPEN_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.skillIdfrom the executing request only if it is missing. - The
RunSkillResponseincludesidentityonly if downstream returns one. If downstream does not provideidentity, 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
serverToolson the request, onmodelConfig, in Cataloxdata.runtime.serverTools, or via clientdefaultServerTools(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:
flexMd.payloads- Markdown section headings (
### Short Answer→shortAnswer)
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 responseToken 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+reviewOutputskills/re-consider.rewrite-scope— rewrite one selected scope →correctedScopeskills/re-consider.rewrite-full— full rewrite →correctedAnswerskills/re-consider.calibrate-decision— calibrate decision/priority/ranking →calibratedOutput- Additional catalog rows / forks: use
listCatalogSkills(),listPublishedSkills(), or CataloxlistCatalogItemsfor 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:verifyRunnable 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:warnProgrammatic 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 credentialsLive 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-skillslist — read-only catalog list sanity check. - Catalox
ai-engineslist — verifiesnpm run catalox:provision-ai-engineswrote theai-gatewayrow (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/modelIdare required (concrete wire id). Missing or alias-shaped values throw beforegateway.invoke(). See docs/MODEL_INVOKE_CONTRACT.md.maxTokenson the request are stripped; Optimixer sets the cap from Catalox catalog fields and resolvedreasoningEffort.reasoningEfforton the request overrides the Catalox catalog default for that skill key.- This package does not merge a global default
modelConfigat construction time; onlytemplateRenderinghas 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 onmodelConfigbeforerunSkill(). 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. OptionaloutputContractenrichesparsedand 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 withstatus === "published".listCatalogSkills(options?)— all catalog rows (any status); optional Catalox list query options.upsertSkillCatalogItem(input, options?)— create/update catalog metadata and template bodies;options.ifNotExistsenforces create-only.softDeleteSkillCatalogItem(skillKey)— clears template and audit text and setsplanned.
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()— Cataloxai-skillscatalog 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 Answer → fullAnswer) 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.jsonengines; aligns with@x12i/catalox) @x12i/catalox≥ 4 and Firebase access for theai-skillsnative catalog (seecreateCataloxFromEnvand Environment (Firebase & Catalox v4))@x12i/ai-gateway^10.4.4 (seepackage.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.
Related runtime packages
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’sinputs.outputContractonrunSkillwhen Run Analysis or downstream validation require structuredparsedfields@exellix/exellix-runtime— root runtime composition (for example loading composedruntimeObjectsfor 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
