Crux
API Reference@crux/core

Skills

Markdown-based skill loading for Crux agents. Compatible with skills.sh.

import { registry, skill, skillsSh } from '@crux/core/skill'

Skills are Markdown instruction sets that an LLM loads on-demand. When placed in a prompt's use array, the resolution pipeline generates a skill index in the system prompt and injects LoadSkill/LoadReference tools. Loaded skills are injected at the system prompt level via executor re-resolution.

skill.inline(config)

Create a skill from inline text.

FieldTypeDescription
idstringRequired. Skill identifier
descriptionstringRequired. One-line description shown in index
instructionsstringRequired. The skill's instruction content
referencesRecord<string, string>?Optional reference files (name -> content)

Returns: Skill — frozen object with _tag: 'Skill'.

const tone = skill.inline({
  id: 'tone',
  description: 'Writing tone guidelines',
  instructions: 'Always write in a warm professional tone.',
})

skill.fromFile(path)

Load a SKILL.md file. Reads synchronously at import time. Parses YAML frontmatter.

ParameterTypeDescription
pathstringPath to the SKILL.md file

Automatically discovers .md files in a sibling references/ directory.

Throws: SkillLoadError if file not found or frontmatter invalid.

const seo = skill.fromFile('./skills/seo-analysis/SKILL.md')

SKILL.md Format

Follows the skills.sh community standard:

---
name: seo-analysis
description: Analyze and optimize content for search engines
version: 1.0.0
license: Apache-2.0
tags: seo, content
---
# SEO Analysis

Instructions here...

Parsed fields: name, description, version, license, tags. Ignored fields: allowed-tools, model, argument-hint, user-invocable (IDE-specific).

skill.fromRegistry(registry, path)

Load a skill from a registry. Content is fetched lazily on first prompt.resolve(), then cached in-memory.

FormTypeDescription
skill.fromRegistry(registry, path)Registry, stringBinds the registry by reference at the call site and loads the registry-local path.
// From skills.sh (built-in)
const seo = skill.fromRegistry(skillsSh, 'mattpocock/skills/seo-analysis')

// From a custom registry value
const acme = registry({ name: 'acme', baseUrl: 'https://skills.acme.corp' })
const brand = skill.fromRegistry(acme, 'brand-guidelines')

Throws: SkillLoadError on missing path, network failure, 404, or registry protocol errors.

registry(config)

Define a custom skill registry using the .well-known/agent-skills/ protocol.

FieldTypeDescription
namestringRegistry name (used as prefix)
baseUrlstringRegistry base URL
auth() => string?Optional auth token provider
const acme = registry({
  name: 'acme',
  baseUrl: 'https://skills.acme.corp',
  auth: () => process.env.SKILLS_TOKEN,
})

registry() returns a registry value. Keep exported registry values in normal TypeScript source so local tooling can inspect them; do not repeat registries in crux.config.ts. Runtime code should create registry-backed skills from the registry value directly because Crux does not scan project files to locate custom registries.

export const acme = registry({
  name: 'acme',
  baseUrl: 'https://skills.acme.corp',
})

export const brand = skill.fromRegistry(acme, 'brand-guidelines')

Skill Properties

PropertyTypeDescription
_tag'Skill'Discriminant for ContextEntry union
idstringSkill identifier
descriptionstringDescription shown in index
instructionsstringFull instruction content
referencesSkillReference[]Bundled reference files
metaSkillMetaParsed frontmatter metadata
dump()() => stringRaw instruction text (exits skill system)

How It Works

  1. Skills in use: [...] are separated from regular contexts during resolution
  2. A skill index context is auto-generated (priority 90) listing all skills
  3. LoadSkill and LoadReference tools are injected into the resolved tool set
  4. When the LLM calls LoadSkill(name):
    • adapter path (Anthropic/OpenAI/Google): Executor intercepts the tool call, re-resolves the prompt, injects skill content into the system prompt, continues the tool loop. Does not count against maxSteps.
    • @crux/ai path (Vercel AI SDK): wrapGenerate middleware detects newly activated skills and injects content into the system prompt on the next model step.
    • Convex Agent path: convexAgent() stores a skill activation snapshot and re-resolves on later turns, picking up activated skills automatically.
  5. LoadReference(skillName, refName) returns reference content as a normal tool result.

createSkillActivationSession(options)

Creates the internal session boundary that powers skill loading. Most app code does not need to call it directly, but external framework adapters can use it to share the same semantics as Crux adapters.

import { createSkillActivationSession } from '@crux/core/skill'

const session = createSkillActivationSession({
  skills,
  initial: {
    activeSkillIds: ['seo'],
    injectedSkillIds: ['seo'],
  },
})

session.activate('brand')
session.resolveInput({ message: 'Draft a landing page' })
session.loadedContexts()
session.tools()
session.snapshot()

createSkillActivationSession.forTarget({ skills, target, persistence }) loads a SkillActivationSnapshot from a SkillActivationPersistence port before creating the session.

SkillLoadError

Thrown when skill loading fails. Extends Error with _tag: 'SkillLoadError'.

import { SkillLoadError } from '@crux/core/skill'
PropertyTypeDescription
skillIdstringThe skill that failed to load
reasonstringHuman-readable error reason

createAgentSkillKit(prompt, options)

Helper for agent frameworks that manage their own tool loop (Convex Agent, Mastra, etc.). Handles pre-resolve, tool extraction, persistence wrapping, and input enhancement.

ParameterTypeDescription
promptPromptA Crux prompt with skills in its use array
optionsAgentSkillKitOptions{ target, persistence, resolveInput? } snapshot port

Returns: AgentSkillKit with:

PropertyTypeDescription
toolsRecord<string, SkillToolDef>LoadSkill (with persistence) + LoadReference
resolveInput(baseInput)(input) => Promise<input>Enhances input with _crux_activeSkills
getActiveIds()() => Promise<string[]>Current active skill IDs

Only needed for external agent frameworks (Convex Agent, Mastra, etc.) that control their own tool loop. Not needed for @crux/anthropic, @crux/openai, @crux/google, or @crux/ai — those handle re-resolution automatically.

See the Agent framework integration guide for a complete end-to-end example with Convex Agent.

import { createAgentSkillKit } from '@crux/core/skill'

// In your agent factory function (called per conversation/thread):
const kit = await createAgentSkillKit(myPrompt, {
  target: { threadId },
  persistence: {
    load: async () => {
      // Called before pre-resolve and each later resolveInput() call.
      return (await store.get('activeSkills')) ?? null
    },
    save: async (_target, snapshot) => {
      // Called after LoadSkill succeeds.
      await store.set('activeSkills', snapshot)
    },
  },
})

// Merge skill tools with your agent's tools
const allTools = { ...myTools, ...kit.tools }

// In your context handler / per-turn resolve:
const resolved = await myPrompt.resolve({
  input: await kit.resolveInput(dynamicData),
})

Instrumentation Hooks

HookFired when
onSkillLoadLoadSkill tool is intercepted
onSkillCacheHitRegistry skill served from cache
onSkillCacheMissRegistry skill requires fresh fetch
onSkillResolveSkill content injected into system prompt

On this page