Skip to main content
Tools & Extensions

Skills

In this page, the "main session" is the session driven directly by query() (or Python's QoderSDKClient), as opposed to subagents delegated via the Agent tool. See Subagents for how the two relate. options.skills controls the main session's skill context and the Skill tool invocation policy. With a list of strings, the SDK issues the main-session skill allowlist and compiles each entry into Skill(name) merged into the tool allowlist (allowedTools / allowed_tools); with 'all', all discovered skills may be invoked and the main-session context is not additionally filtered.

SDK Does Not Load Built-in Skills

The SDK always appends --disable-builtin-skills when launching the CLI, so sessions never get the CLI's factory built-in skills (simplify, debug, security-review, quest, batch, agent-creator, hook-config, mcp-config, skill-creator, etc.). The discovery list (TypeScript's initializationResult().skills, Python's get_server_info()['skills']) contains no source: 'built-in' entries either, and the model system prompt can't see them. This is fixed SDK behavior with no opt-in; if you want the capability of a CLI built-in skill, either ship your own copy of the SKILL.md via a plugin / user dir / project dir, or run the CLI directly outside the SDK. The session can still pick up skills contributed from these sources:
  • plugin skills: loaded via options.plugins, addressed with the plugin-qualified name (plugin:skill).
  • User / project skills: discovered after explicitly enabling user / project / local via the setting sources option.
  • agent-preloaded skills: declared on options.agents[name].skills, scoped to that sub-agent only.
To know reliably which skills the session discovered, read the discovery list at runtime instead of hard-coding the set.

Using CLI Default Policy

Without skills, the SDK injects no extra Skill allowlist and defers entirely to CLI policy. Since built-in skills are disabled, sessions without setting sources / plugins have an empty discovery list.
import { query } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: 'Analyze the test coverage of this project',
  options: {
    cwd: '/path/to/project',
  },
});

Enabling All Discovered Skills

const q = query({
  prompt: 'Use an appropriate skill to perform a code review',
  options: {
    cwd: '/path/to/project',
    settingSources: ['project'],
    skills: 'all',
  },
});
skills set to 'all' lets the Skill tool invoke every skill the CLI currently discovers (sources determined by setting sources / plugins; built-ins are no longer included).

Enabling Only Specific Skills

const q = query({
  prompt: 'Use the review skill to inspect recent changes',
  options: {
    cwd: '/path/to/project',
    settingSources: ['project'],
    skills: ['review'],
  },
});
With a string list, only matching skills appear in the main model's skill listing and can be invoked through the Skill tool. Entries may be bare or plugin-qualified names. Passing an empty list [] hides and rejects every skill in the main session. The list doesn't change CLI discovery; unlisted skills may still appear in the discovery list.

Enabling Plugin Skills

Plugin skills use the plugin-qualified name plugin:skill. For plugin loading methods, see Plugins documentation.
const q = query({
  prompt: 'Use the echo skill provided by the plugin to handle this input',
  options: {
    plugins: [{ type: 'local', path: '/path/to/sdk-test-plugin' }],
    skills: ['sdk-test-plugin:sdk-echo'],
  },
});

Merging with Explicit Tool Allowlist

const q = query({
  prompt: 'Read the source and use the review skill to produce a list of issues',
  options: {
    cwd: '/path/to/project',
    settingSources: ['project'],
    allowedTools: ['Read', 'Grep'],
    skills: ['review'],
  },
});
The configuration above ultimately allows the listed read/search tools plus Skill(review). The SDK merges and dedupes—no duplicate entries are written.

Hiding Discovered Skills

The string-list form of options.skills filters the main-session model context and restricts Skill tool calls, but doesn't change discovery results. To hide a plugin / user / project skill from the discovery list as well, use settings.skillOverrides.
const q = query({
  prompt: 'Handle this task',
  options: {
    plugins: [{ type: 'local', path: '/path/to/sdk-test-plugin' }],
    settings: {
      skillOverrides: {
        'sdk-test-plugin:sdk-echo': 'off',
      },
    },
  },
});
  • 'off': fully hidden—absent from the discovery list and the model system prompt, and Skill tool calls are rejected.
  • Other values: 'on' (default), 'name-only' (only shows name, not description), 'user-invocable-only' (invisible to model, user can still trigger via /name).
  • Scope of effect: every SDK-visible source (plugin, user, project, …) respects this override; CLI built-ins are already blocked by --disable-builtin-skills, so overrides for them have nothing to act on.
  • Key naming rules: Plugin skills use the plugin-qualified name plugin:skill; non-plugin skills use bare names. Both forms can be written simultaneously; matching is attempted against the fully qualified name first, then falls back to the bare name.
options.skills can hide the main session's model context but doesn't filter the discovery list; to hide from the discovery list too, use skillOverrides: { name: 'off' }.

Reading Skills Discovered in the Current Session

The initialization result contains the full skill list the CLI discovered in this session, unfiltered by the string-list form of options.skills. It suits host UIs showing "discovered skills" and is not the main session's current invocable list. In Python, query() is a one-shot stream with no convenient query API—use QoderSDKClient to read it after the handshake.
const q = query({
  prompt: 'Do not execute any task yet',
  options: {
    cwd: '/path/to/project',
    settingSources: ['project'],
    skills: 'all',
  },
});

const init = await q.initializationResult();
console.log(init.skills?.map((skill) => skill.name));
A string-list skills controls main-session context and tool visibility; it is not a security boundary. Unlisted skills do not appear in the model's skill listing and cannot be invoked through the Skill tool, but their files remain on disk and can still be accessed by regular file tools.

Custom Agent Preloading Skills

If you define custom sub-Agents via options.agents, you can declare skills in the Agent definition. When the main session invokes the Agent tool, the sub-Agent will run with the specified skills loaded.
const q = query({
  prompt: 'Dispatch a helper agent that uses the sdk-agent-marker skill to return the marker',
  options: {
    cwd: '/path/to/project',
    allowedTools: ['Agent'],
    agents: {
      'sdk-skill-helper': {
        description: 'Invoke when the sdk-agent-marker skill is needed.',
        prompt: 'You are a helper agent that only reads and runs the specified skill.',
        skills: ['sdk-agent-marker'],
        maxTurns: 2,
      },
    },
  },
});
Such skills only affect that agent's context and don't enable the same skills for the main session—the main session's tool allowlist is untouched by this change.

Options Reference

Field (TypeScript / Python)Description
skills / skillsList restricts the main session's skill context and invocation; [] disables all; 'all' enables every discovered skill
agents / agentsCustom agents; an agent definition can declare its own skills preload list
allowedTools / allowed_toolsTool allowlist; merged and deduped with Skill(...) entries compiled from skills
settingSources / setting_sourcesDecides whether the CLI scans user / project directories for skills (default empty = sandboxed)
plugins / pluginsLoads plugins; their skills join the discovery set
settings also has several skill-related fields that the SDK passes through; actual effects depend on whether the CLI version implements them:
FieldPurpose
skillOverridesSet 'on' | 'name-only' | 'user-invocable-only' | 'off' per skill name; plugin, user, project sources all respect this override
skillListingMaxDescCharsCharacter limit per description in the skill listing; the SDK passes it through and the default depends on the CLI version
skillListingBudgetFractionContext-window fraction reserved for the skill listing; the SDK passes it through and the default depends on the CLI version

Return Value Reference

type SDKControlInitializeResponse = {
  skills?: Array<{ name: string; description?: string; source?: string }>;
  // ...also returns commands / agents / models and similar fields
};
Only skill-related fields are shown; full type in SDK References. In Python, the streaming SystemMessage(subtype="init").data["skills"] is a list of skill names—not the same structure as this metadata-rich discovery list.

Best Practices

  • Enable skills deliberately: 'all' suits development and debugging; end-user products should usually pass an explicit list.
  • Recreate built-in skill behavior yourself if you want it: the SDK won't inject simplify / security-review into sessions; provide your own SKILL.md within plugin or setting-sources scope.
  • Don't treat skills as a sandbox: security boundaries belong to tool allow/deny lists, the permission callback, permission modes, and sandboxing combined.
  • Use the discovery list for UIs: initializationResult().skills / get_server_info()['skills'] is the stable entry into CLI discovery—use it to show "discovered skills", not as the main session's invocable set.
  • Manage sub-Agent skills separately: They are independent lists from the main session's options.skills and do not override each other.

Current Limitations

  • --disable-slash-commands is the CLI's switch to disable all slash-command skills at once; the SDK currently exposes no first-class option—avoid relying on non-public passthrough paths.
  • settings.skillListingMaxDescChars and settings.skillListingBudgetFraction are listing-budget control fields that the SDK passes through; the current qoderclicn has not implemented listing budget control, so passing them does not raise an error but also does not change behavior.
  • The Python SDK does not yet provide a client.supported_skills() convenience method; read from get_server_info()['skills'] instead (already on the backlog).