> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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](/en/cli/sdk/python/agents) 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.

<div id="sdk-does-not-load-built-in-skills" />

## 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.

***

<div id="using-cli-default-policy" />

## 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.

```python theme={null}
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)
```

<div id="enabling-all-discovered-skills" />

## Enabling All Discovered Skills

```python theme={null}
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).

<div id="enabling-only-specific-skills" />

## Enabling Only Specific Skills

```python theme={null}
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']`](#reading-skills-discovered-in-the-current-session).

<div id="enabling-plugin-skills" />

## Enabling Plugin Skills

Plugin skills use the plugin-qualified name `plugin:skill`. For plugin loading methods, see [Plugins documentation](/en/cli/sdk/python/plugins).

```python theme={null}
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)
```

<div id="merging-with-explicit-tool-allowlist" />

## Merging with Explicit Tool Allowlist

```python theme={null}
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.

<div id="hiding-discovered-skills" />

## 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`](/en/cli/sdk/python/references#skilloverrides).

```python theme={null}
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"}`.

***

<div id="reading-skills-discovered-in-the-current-session" />

## 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.

```python theme={null}
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.

***

<div id="custom-agent-preloading-skills" />

## 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.

```python theme={null}
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.

***

<div id="options-reference" />

## Options Reference

| Field             | Type                                                | Description                                                                                                           |
| ----------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `skills`          | `list[str] \| Literal["all"] \| None`               | A list restricts main-session skill context and invocation; `[]` disables all; `"all"` enables every discovered skill |
| `agents`          | `dict[str, AgentDefinition] \| None`                | Custom Agents; `AgentDefinition.skills` is each Agent's independent preload list                                      |
| `allowed_tools`   | `list[str]`                                         | Tool allowlist; merged with `Skill(...)` entries compiled from `skills` with deduplication                            |
| `setting_sources` | `list[Literal["user", "project", "local"]] \| None` | Controls whether the CLI scans user / project directories for skills; an empty list scans none of these sources       |
| `plugins`         | `list[PluginSpec] \| None`                          | Loads 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:

| Field                        | Purpose                                                                                                                                        |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `skillOverrides`             | Set `"on" \| "name-only" \| "user-invocable-only" \| "off"` per skill name; plugin, user, project, and other sources all respect this override |
| `skillListingMaxDescChars`   | Character limit per description in the skill listing; the SDK passes it through and the CLI version determines the default                     |
| `skillListingBudgetFraction` | Context-window fraction reserved for the skill listing; the SDK passes it through and the CLI version determines the default                   |

***

<div id="return-value-reference" />

## Return Value Reference

The dict returned by `client.get_server_info()` includes:

```python theme={null}
{
    "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`](/en/cli/sdk/python/references#sdkcontrolinitializeresponse) for the full type. The streaming `SystemMessage(subtype="init").data["skills"]` value is a list of skill names, not this metadata inventory.

***

<div id="best-practices" />

## 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.

***

<div id="current-limitations" />

## 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).
