Skip to main content
Extensions

Skills

In this guide, "main session" means the session driven directly by query() or QoderSDKClient, as opposed to a subagent delegated through the Agent tool. See Subagents for how the two relate. options.skills controls the main session's skill context and Skill tool invocation policy. When you pass a string list, the SDK sends it as the main-session skill allowlist and also compiles each entry to Skill(name) before merging it with allowed_tools. Passing "all" permits every discovered skill without further filtering the main-session context.

SDK Does Not Load Built-in Skills

When the SDK launches the CLI it always appends --disable-builtin-skills, so the session never sees the CLI's factory built-in skills (simplify, debug, security-review, quest, batch, agent-creator, hook-config, mcp-config, skill-creator, etc.). No source: 'built-in' entries appear in get_server_info()['skills'], and the model's system prompt does not see them either. 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 just reuse the CLI's default scenarios. 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 when you opt into user / project / local via options.setting_sources.
  • Agent-preloaded skills: declared on options.agents[name].skills, scoped to that subagent only.
To reliably confirm what was discovered in a session, read client.get_server_info()['skills'] at runtime — don't hard-code the set.

Using CLI Default Policy

When skills is not passed, the SDK does not inject an additional Skill allowlist, leaving everything to the CLI's own policy. Because built-ins are disabled, a session with no setting_sources / plugins will see an empty get_server_info()['skills'] list.
from qoder_agent_sdk import query, QoderAgentOptions

async for msg in query(
    prompt="Analyze the test coverage of this project",
    options=QoderAgentOptions(cwd="/path/to/project"),
):
    print(msg)

Enabling All Discovered Skills

async for msg in query(
    prompt="Use an appropriate skill to perform a code review",
    options=QoderAgentOptions(
        cwd="/path/to/project",
        setting_sources=["project"],
        skills="all",
    ),
):
    print(msg)
skills="all" allows the Skill tool to invoke every skill the CLI currently discovers (sources are determined by setting_sources / plugins; built-ins are no longer included).

Enabling Only Specific Skills

async for msg in query(
    prompt="Use the review skill to inspect recent changes",
    options=QoderAgentOptions(
        cwd="/path/to/project",
        setting_sources=["project"],
        skills=["review"],
    ),
):
    print(msg)
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. This list does not change CLI discovery. Unlisted skills may still appear in client.get_server_info()['skills'].

Enabling Plugin Skills

Plugin skills use the plugin-qualified name plugin:skill. For plugin loading methods, see Plugins documentation.
async for msg in query(
    prompt="Use the echo skill provided by the plugin to handle this input",
    options=QoderAgentOptions(
        plugins=[{"type": "local", "path": "/path/to/sdk-test-plugin"}],
        skills=["sdk-test-plugin:sdk-echo"],
    ),
):
    print(msg)

Merging with Explicit Tool Allowlist

async for msg in query(
    prompt="Read the source and use the review skill to produce a list of issues",
    options=QoderAgentOptions(
        cwd="/path/to/project",
        setting_sources=["project"],
        allowed_tools=["FileRead", "Grep"],
        skills=["review"],
    ),
):
    print(msg)
The above configuration ultimately allows FileRead, Grep, and Skill(review). The SDK merges and deduplicates entries, so it never writes duplicates with the same name.

Hiding Discovered Skills

A string-list options.skills filters the main model's context and limits Skill tool calls, but it does not change the discovery inventory in client.get_server_info()['skills']. To remove a plugin / user / project skill from the discovery inventory as well, use settings.skillOverrides.
options = QoderAgentOptions(
    plugins=[{"type": "local", "path": "/path/to/sdk-test-plugin"}],
    settings={
        "skillOverrides": {
            "sdk-test-plugin:sdk-echo": "off",
        },
    },
)
  • "off": Completely hidden — not in get_server_info()['skills'], not in the model system prompt; Skill tool invocations are also rejected.
  • Other values: "on" (default), "name-only" (shows name only, not description), "user-invocable-only" (invisible to the model; the user can still trigger it via /name).
  • Scope of effect: every SDK-visible source (plugin, user, project, etc.) 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 skills from the main model's context, but it does not filter the get_server_info()['skills'] discovery inventory. To hide a skill from both, use skillOverrides: {name: "off"}.

Reading Skills Discovered in the Current Session

The initialization result includes the complete skill inventory discovered by the CLI in this session. A string-list options.skills does not filter this inventory. Host UIs can display it as "discovered skills," but should not treat it as the main session's currently invokable set. query() is a one-shot stream with no convenience query interface; use QoderSDKClient to read after the handshake.
from qoder_agent_sdk import QoderAgentOptions, QoderSDKClient

options = QoderAgentOptions(
    cwd="/path/to/project",
    setting_sources=["project"],
    skills="all",
)

async with QoderSDKClient(options) as client:
    info = await client.get_server_info()
    if info:
        for skill in info.get("skills", []):
            print(skill["name"], skill.get("source"))
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 subagents via options.agents, you can declare skills in the AgentDefinition. When the main session invokes the Agent tool, the subagent will run with the specified skills loaded.
from qoder_agent_sdk import AgentDefinition, QoderAgentOptions

options = QoderAgentOptions(
    cwd="/path/to/project",
    allowed_tools=["Agent"],
    agents={
        "sdk-skill-helper": AgentDefinition(
            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,
        ),
    },
)
These skills only affect that Agent's context and are not equivalent to enabling the same-named skill for the main session — the main session's allowed_tools is not affected by this change.

Options Reference

FieldTypeDescription
skillslist[str] | Literal["all"] | NoneA list restricts main-session skill context and invocation; [] disables all; "all" enables every discovered skill
agentsdict[str, AgentDefinition] | NoneCustom Agents; AgentDefinition.skills is each Agent's independent preload list
allowed_toolslist[str]Tool allowlist; merged with Skill(...) entries compiled from skills with deduplication
setting_sourceslist[Literal["user", "project", "local"]] | NoneControls whether the CLI scans user / project directories for skills; an empty list scans none of these sources
pluginslist[PluginSpec] | NoneLoads plugins; skills inside contribute to the discovered set
settings (dict / path / JSON string) has several skill-related fields that the SDK passes through as a dict; 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, and other sources all respect this override
skillListingMaxDescCharsCharacter limit per description in the skill listing; the SDK passes it through and the CLI version determines the default
skillListingBudgetFractionContext-window fraction reserved for the skill listing; the SDK passes it through and the CLI version determines the default

Return Value Reference

The dict returned by client.get_server_info() includes:
{
    "commands": [{"name": str, "description": str, ...}, ...],
    "agents": [{"name": str, "description": str, "model": str | None}, ...],
    "skills": [{"name": str, "description": str | None, "source": str | None}, ...],
    # Also includes models / account / output_style and other fields
}
Only skill-related fields are shown here. See SDKControlInitializeResponse for the full type. The streaming SystemMessage(subtype="init").data["skills"] value is a list of skill names, not this metadata inventory.

Best Practices

  • Enable skills as needed: skills="all" is ideal for development and debugging; end-user-facing products should typically pass an explicit list.
  • Want a CLI built-in's behavior? Ship your own copy: the SDK will not inject simplify / security-review and the rest. Provide your own SKILL.md via a plugin or a setting_sources-visible directory.
  • Don't treat skills as a security boundary: It only filters skill discovery and invocation. Use allowed_tools, disallowed_tools, can_use_tool, permission mode, and isolation provided by the host environment to control access.
  • Use get_server_info()['skills'] for the UI: This is the stable entry point for the CLI discovery pipeline and represents "discovered skills," not every skill currently invokable in the main session.
  • Manage subagent 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 a CLI capability for one-shot disabling of all slash-command skills; the SDK does not currently expose a first-class option, and depending on non-public paths like extra_args is not recommended.
  • settings.skillListingMaxDescChars and settings.skillListingBudgetFraction are listing-budget control fields that the SDK passes through; the current qodercli 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).