Crux
GuidesSkills

Agent framework integration

Use createAgentSkillKit() to wire skills into Convex Agent, Mastra, or any framework that controls its own tool loop.

You added skills to a prompt, pointed Convex Agent or Mastra at it, and they silently never load: LoadSkill may even fire, but the loaded skill vanishes by the next turn. The cause is who owns the tool loop. With a Crux adapter (@use-crux/anthropic, @use-crux/openai, @use-crux/google, @use-crux/ai), skills are fully automatic because Crux owns the loop and can intercept LoadSkill mid-call and re-resolve the system prompt. External agent frameworks decide themselves when to call the model, when to execute tools, and how the system prompt is assembled per turn, so Crux cannot reach inside.

createAgentSkillKit() is the fix: it gives you the same session-backed LoadSkill semantics, driven through a persistence port you provide.

import { createAgentSkillKit } from "@use-crux/core/skill";

// In your agent factory function (called per conversation/thread):
const kit = await createAgentSkillKit(myPrompt, {
  target: { threadId },
  persistence: {
    load: async () => (await records.get("activeSkills")) ?? null,
    save: async (_target, snapshot) => {
      await records.put("activeSkills", snapshot);
    },
  },
});

// Merge skill tools into your agent's tool set.
const allTools = { ...myTools, ...kit.tools };

// At the start of each turn, before resolving the prompt:
const resolved = await myPrompt.resolve({
  input: await kit.resolveInput(dynamicData),
});

The Problem the Kit Solves

Without the kit, integrating skills into a framework like Convex Agent means doing all of this yourself:

  1. Pre-resolve the Crux prompt to extract LoadSkill / LoadReference tools
  2. Convert those tools to the framework's tool format
  3. Wrap LoadSkill with session snapshot persistence so loaded skills survive across turns
  4. Read persisted skill IDs at the start of each turn
  5. Pass them into the resolve input so the system prompt includes loaded skill content

createAgentSkillKit() handles steps 1, 2, 4, and 5. You provide step 3 (the persistence layer) as a SkillActivationPersistence port that stores the full SkillActivationSnapshot.

What the Kit Gives You

PropertyWhat it does
kit.toolsSession-bound LoadSkill and LoadReference, ready to merge into your agent's tool set.
kit.resolveInput(baseInput)Takes your per-turn dynamic input and returns it with _crux_activeSkills injected. Call this before prompt.resolve().
kit.getActiveIds()Returns the currently active skill IDs from your persistence layer. Useful for debugging and UI.

The preferred persistence port stores full SkillActivationSnapshot values: load(target) returns the snapshot (or null) before pre-resolve and each resolveInput() call, and save(target, snapshot) persists it after LoadSkill succeeds. See the SkillActivationPersistence reference for the exact port contract.

Convex Agent

For Convex Agent, use @use-crux/convex/agent and pass skill tools through convexTools() so skill activation/deactivation remains observable in the Crux trace. See the Convex Agent, tools, and skills guide for the full setup.

Behavior Across Turns

Frameworks that compose the system prompt before running tools have one quirk worth understanding: when LoadSkill fires, the system prompt for that turn was already composed. So the skill content reaches the model two different ways depending on the turn:

TurnWhere the skill content lives
The turn LoadSkill is called onTool result: immediate and functional, but presented as supplementary information
Every subsequent turnSystem prompt: authoritative and persistent, via kit.resolveInput()

In practice this is rarely a problem: the tool-result version is still rich enough to act on, and by the next turn the skill is in its proper authoritative position. If you need strictly-authoritative behavior on the loading turn itself, switch to a Crux adapter, which can re-resolve mid-loop.

Persistence Choices

The kit is intentionally agnostic about where activation snapshots live. A few patterns:

StorageGood for
Convex contract store keyed by threadConvex backends, co-locates skill state with the thread
A field on the thread documentSimplest if you already store thread metadata
RedisStateless function runtimes that need fast per-conversation lookups
In-memory mapLocal development, tests, or single-process agents

Whatever you pick, load() and save() must agree on the storage: they are called from the same conversation but at different points in the request lifecycle.

On this page