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

# Skills

Skills package task instructions, reference material, and optional scripts in reusable directories. Agents can discover skills before loading their full instructions. Code execution is involved only when a skill uses scripts and an executor is configured

| Use case | Integration |
| :- | :- |
| Local skills maintained with the application | `load_skill_from_dir` and `SkillToolset` |
| On-demand access to cloud skill spaces | `VeSkillRegistry` and `SkillToolset` |
| Shared skills in a deployed Harness | Harness `skills` configuration |
| Remote skill workflows | `skills_sandbox` mode or a [remote sandbox agent](https://docs.veadk.xyz/productions/veadk/archives/1.1.26/en/components/agent/remote-sandbox-agent.md) |

The `SkillToolset` examples use the Google ADK 2.2 API. Complete [installation and model configuration](https://docs.veadk.xyz/productions/veadk/archives/1.1.26/en/get-started/quickstart.md) first

## Local skills

Create `skills/incident-summary/SKILL.md` in your project:

```markdown skills/incident-summary/SKILL.md lines theme={null}
---
name: incident-summary
description: Summarize incident notes into impact, timeline, and follow-up actions
---

# Incident summary

Read the notes provided by the user and return three sections:

- Impact: describe affected users and services
- Timeline: list only times explicitly present in the notes
- Follow-up actions: distinguish completed work from proposed work

Mark missing information as unknown. Do not invent causes or timestamps.
```

Create and run `main.py` from the project root:

```python main.py lines theme={null}
import asyncio
from pathlib import Path
from google.adk.skills import load_skill_from_dir
from google.adk.tools.skill_toolset import SkillToolset
from veadk import Agent, Runner

skill_dir = Path(__file__).parent / "skills" / "incident-summary"
skill = load_skill_from_dir(skill_dir)
agent = Agent(
    name="incident_assistant",
    instruction="Use the incident-summary skill to summarize incident notes.",
    tools=[SkillToolset(skills=[skill])],
)

async def main():
    print(await Runner(agent=agent).run(
        messages="At 09:10 invoice downloads failed. At 09:25 a rollback restored service. The cause is unknown.",
        session_id="skills-demo",
    ))

if __name__ == "__main__":
    asyncio.run(main())
```

The response should contain impact, timeline, and follow-up sections, marking missing information as unknown. This skill only processes supplied text and needs no code executor

Default tools are `list_skills`, `load_skill`, `load_skill_resource`, and `run_skill_script`. Configuring a registry also adds `search_skills`

### SkillToolset parameters

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `skills` | `list[Skill] \| None` | `None` | Loaded local skills; names must be unique |
| `registry` | `SkillRegistry \| None` | `None` | Source for on-demand discovery and loading |
| `code_executor` | `BaseCodeExecutor \| None` | `None` | Script executor; falls back to the agent's executor when omitted |
| `script_timeout` | `int` | `300` | Shell script timeout in seconds; does not limit Python scripts executed directly by the executor |
| `additional_tools` | `list[ToolUnion] \| None` | `None` | Tools activated through skill `metadata.adk_additional_tools` |
| `tool_name_prefix` | `str \| None` | `None` | Prefix for skill tool names |
| `tool_filter` | `ToolPredicate \| list[str] \| None` | `None` | Restricts tools by predicate or name |

### Run skill scripts

> [!WARNING]
>
> `UnsafeLocalCodeExecutor` runs skill code in the current process environment without sandbox isolation. Enable it only for reviewed, trusted skills: code can access the process's files, network, and credentials. Use a restricted container or remote sandbox when isolation is required

> [!NOTE]
>
> When a shell script times out or is cancelled, the entire process group (including child processes spawned by the script) is terminated with up to 5 seconds for cleanup, preventing orphaned processes

For a skill with executable `scripts/`, replace the toolset and agent configuration after loading `skill` above:

```python lines theme={null}
from google.adk.code_executors import UnsafeLocalCodeExecutor

toolset = SkillToolset(
    skills=[skill],
    code_executor=UnsafeLocalCodeExecutor(),
    script_timeout=300,
)
agent = Agent(name="skills_assistant", tools=[toolset])
```

Creating `SkillToolset` directly does not install script dependencies. Relevant loaders in Harness, CLI-generated code, and AgentKit session capability services configure a local executor. Review skill sources before using those entry points as well

## Cloud skill spaces

Prepare a readable source ID and account credentials permitted to list skills and download their files. `SKILL_SOURCE_ID` is an environment variable defined by this example. Each `VeSkillRegistry` accepts exactly one source ID, not a comma-separated list

| Setting | Volcengine | BytePlus |
| :- | :- | :- |
| `CLOUD_PROVIDER` | `volcengine`, default | `byteplus` |
| AccessKey | `VOLCENGINE_ACCESS_KEY` | `BYTEPLUS_ACCESS_KEY` |
| SecretKey | `VOLCENGINE_SECRET_KEY` | `BYTEPLUS_SECRET_KEY` |
| Temporary credential Token | `VOLCENGINE_SESSION_TOKEN` | `BYTEPLUS_SESSION_TOKEN` |
| Default AgentKit skill-space region | `cn-beijing` | `ap-southeast-1` |

If the complete AK/SK pair is absent, the loader tries the environment's IAM Role credentials. Set `AGENTKIT_TOOL_REGION` to select an AgentKit skill-space region. Credentials, resources, region, and platform must match. SkillHub and AgentKit skill-space availability depend on the services enabled for the account

```bash lines theme={null}
export SKILL_SOURCE_ID="<skill-space-id>"
```

```python cloud_skills.py lines theme={null}
import asyncio
import os
from google.adk.tools.skill_toolset import SkillToolset
from veadk import Agent, Runner
from veadk.skills import VeSkillRegistry

registry = VeSkillRegistry(skill_source_id=os.environ["SKILL_SOURCE_ID"])
agent = Agent(
    name="skills_assistant",
    instruction="Search available skills and load the relevant instructions before answering.",
    tools=[SkillToolset(registry=registry)],
)

async def main():
    print(await Runner(agent=agent).run(
        messages="List the available skills and explain what they help with.",
        session_id="registry-demo",
    ))

if __name__ == "__main__":
    asyncio.run(main())
```

Run `python cloud_skills.py` to inspect discoverable skills

| VeSkillRegistry parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `skill_source_id` | `str` | Required | One source ID; `sp-` identifies a SkillHub space, while other IDs use AgentKit skill-space behavior |
| `cache_dir` | `Path \| None` | `None` | Download cache; defaults to `VEADK_SKILLS_CACHE_DIR`, then `veadk/skills` under the system temporary directory |

Creating a registry does not download every skill. `search_skills` fetches all source metadata and currently does not filter by the query. `get_skill` refreshes metadata and downloads the named skill on demand. Cached versions are reused; changed versions are downloaded again. The cache directory must be writable

Nonstandard cloud frontmatter fields such as `allowed-tools`, `triggers`, and `requires` are retained in `metadata`. Retention does not automatically enforce their permission or dependency declarations

### Skill space policy

Set the `SKILL_SPACE_POLICY` environment variable to filter which skills are loaded from cloud skill spaces by skill ID. The variable supports two JSON formats: a global policy and a per-space policy.

#### Global policy

A global policy applies the same filter to all skill spaces. The JSON object contains the following fields:

| Field | Type | Description |
| :- | :- | :- |
| `mode` | `"allow" \| "deny"` | Filter mode |
| `ids` | `list[str]` | Skill IDs participating in the filter |

When `mode` is `allow`, only skills whose IDs are in the list are loaded. When `mode` is `deny`, skills whose IDs are in the list are excluded. When the variable is not set, all skills are loaded

```bash lines theme={null}
export SKILL_SPACE_POLICY='{"mode": "allow", "ids": ["skill-1", "skill-2"]}'
```

#### Per-space policy

A per-space policy configures a separate filter for each skill space, useful when loading skills from multiple skill spaces with different filtering requirements. The JSON object contains the following field:

| Field | Type | Description |
| :- | :- | :- |
| `spaces` | `dict[str, object]` | Keys are skill space IDs; values are filter policy objects for each space |

Each per-space policy object contains the following fields:

| Field | Type | Description |
| :- | :- | :- |
| `mode` | `"allow" \| "deny"` | Filter mode for this space |
| `ids` | `list[str]` | Skill IDs participating in the filter for this space |

When a skill space is not present in `spaces`, skills from that space are not filtered and all are loaded

```bash lines theme={null}
export SKILL_SPACE_POLICY='{"spaces": {"ss-a": {"mode": "allow", "ids": ["skill-1"]}, "ss-b": {"mode": "deny", "ids": ["skill-4"]}}}'
```

> [!NOTE]
>
> The policy value must not exceed 8192 bytes. A malformed policy disables all cloud skill loading to avoid accidentally exposing an unrestricted set of skills

When `SKILL_SPACE_POLICY` is set, VeADK automatically forwards it to remote sandbox sessions so that skill loading inside the sandbox follows the same filter. No manual pass-through is needed when using `execute_skills`, `invoke_skill`, `poll_skill`, or `run_sandbox_agent`; see [Code sandboxes](https://docs.veadk.xyz/productions/veadk/archives/1.1.26/en/components/tools/code-sandbox.md)

> [!NOTE]
>
> A `skill_space_policy` set on an individual `skillspace` entry in Harness `selected_skills` takes precedence over the `SKILL_SPACE_POLICY` environment variable; see [Harness skills center](#harness-skills-center)

## Harness skills center

Create a project and configure deployment credentials using [Harness deployment](https://docs.veadk.xyz/productions/veadk/archives/1.1.26/en/deploy/harness.md). Replace the example names and IDs with accessible skills. Plain names or slugs identify Skill Hub skills; the `space:` prefix identifies an AgentKit skill space

```bash lines theme={null}
cd my-harness
veadk harness add --skills "data-visualization-cloud,space:ss-example"
```

The generated `harness.yaml` stores sources as a list:

```yaml lines theme={null}
skills:
  - data-visualization-cloud
  - space:ss-example
```

Harness loads configured skills on startup; a `space:` source loads every skill in that space. An invocation can add skills for that request:

```bash lines theme={null}
veadk harness invoke   --name research-agent   --message "Summarize the supplied incident notes"   --skills "space:ss-example"
```

| Example option | Purpose |
| :- | :- |
| `add --skills` | Saves base Harness skill sources |
| `invoke --name` | Selects the Harness agent |
| `invoke --message` | Supplies task text |
| `invoke --skills` | Adds skills for this request without changing `harness.yaml` |

If any skill cannot be downloaded, lacks a valid `SKILL.md`, or has an invalid format, loading fails instead of continuing with an incomplete set. Skills-center access requires permission to read the space and its object-storage contents

### Harness skill space filtering

Through `selected_skills` in `harness.yaml` (see [Harness deployment](https://docs.veadk.xyz/productions/veadk/archives/1.1.26/en/deploy/harness.md#structured-resource-configuration)), you can set a session-level filter policy for `skillspace`-source skill spaces. Add a `skill_space_policy` field to a `selected_skills` entry to filter skills by ID within that space without setting the `SKILL_SPACE_POLICY` environment variable

`skill_space_policy` is only valid when `source` is `skillspace` and requires `skill_space_id` to be set

| Field | Type | Default | Description |
| :- | :- | :- | :- |
| `mode` | `"allow" \| "deny"` | — | Filter mode |
| `ids` | `list[str]` | `[]` | Skill IDs participating in the filter |

When `mode` is `allow`, only skills whose IDs are in the list are loaded. When `mode` is `deny`, skills whose IDs are in the list are excluded. Multiple `skillspace` entries can each set their own `skill_space_policy` for independent per-space filtering

> [!NOTE]
>
> When any `selected_skills` entry sets a `skill_space_policy`, Harness uses these policies and ignores the `SKILL_SPACE_POLICY` environment variable

```yaml title="harness.yaml" lines theme={null}
harness:
  selected_skills:
    - source: skillspace
      skill_space_id: ss-a
      skill_space_policy:
        mode: allow
        ids:
          - skill-1
          - skill-2
    - source: skillspace
      skill_space_id: ss-b
      skill_space_policy:
        mode: deny
        ids:
          - skill-4
    - source: skillhub
      slug: team/reporting
```

### Harness TOS skills

`selected_skills` supports `source: tos` to load skills directly from TOS object storage without going through SkillHub or skill spaces. TOS skills can be loaded in two ways:

* **Private TOS directory**: Specify a `tos://<bucket>/<prefix>/` address via `tos_url`. The skill directory is downloaded using the current account's cloud credentials (AK/SK or the IAM Role bound to the runtime environment). The directory root must contain `SKILL.md` or `skill.md`
* **Public TOS zip archive**: Specify an HTTPS address pointing to a `.zip` file via `tos_public_url`. No AK/SK is required. The address must target a TOS public endpoint (hostname containing `tos-` and ending with `.volces.com` or `.bytepluses.com`) and must not include userinfo or signature query parameters

> [!WARNING]
>
> Public TOS zip URLs must not contain any credentials (userinfo or TOS signature query parameters). When loading fails, the error message redacts the TOS reference to avoid exposing parameters from the address

Each `tos` entry loads one skill. To load multiple TOS skills, add a separate `selected_skills` entry for each

| Field | Type | Default | Description |
| :- | :- | :- | :- |
| `source` | `Literal["tos"]` | — | Must be `tos` |
| `tos_url` | `str \| None` | `None` | Private TOS directory address in `tos://<bucket>/<prefix>/` format; mutually exclusive with `tos_public_url` |
| `tos_public_url` | `str \| None` | `None` | Public TOS zip HTTPS URL ending in `.zip`; mutually exclusive with `tos_url` |
| `region` | `str \| None` | `None` | TOS service region for private TOS directory access |
| `version` | `str \| None` | `None` | Skill version identifier for cache control |

> [!NOTE]
>
> Exactly one of `tos_url` and `tos_public_url` must be set; they are mutually exclusive. Private TOS directory downloads are limited to 1024 files and 64 MiB total size; public TOS zip archives have the same limits on extracted file count, uncompressed total size, and download size. Loading fails when any limit is exceeded

```yaml title="harness.yaml" lines theme={null}
harness:
  selected_skills:
    - source: tos
      tos_url: tos://my-bucket/skills/reporting/
      region: cn-beijing
    - source: tos
      tos_public_url: https://my-bucket.tos-cn-beijing.volces.com/skills/reporting.zip
    - source: skillhub
      slug: team/reporting
```

## Skill directory layout

Each skill has its own directory containing at least `SKILL.md`. Its `name` should match the directory name, and its `description` should explain its purpose

```text lines theme={null}
skills/
└── incident-summary/
    ├── SKILL.md
    ├── references/
    ├── assets/
    └── scripts/
```

Use `references/` for material read on demand, `assets/` for templates or media, and `scripts/` for executable scripts. Create these optional directories only as needed and explain when to use their files in the skill instructions

## Legacy local entry (deprecated)

`Agent(skills=..., skills_mode="local")` remains compatible but is deprecated. Migrate local skills to `SkillToolset`

| `skills_mode` | Behavior |
| :- | :- |
| `local` | Legacy local entry; migration recommended |
| `skills_sandbox` | Executes through `execute_skills` in a remote skill sandbox |
| `aio_sandbox` | Uses an AgentKit All-in-one tool sandbox |

Sandbox modes remain available. Before running this example, configure `SKILL_SOURCE_ID`, the model, and the credentials and Tool ID required by [code sandbox](https://docs.veadk.xyz/productions/veadk/archives/1.1.26/en/components/tools/code-sandbox.md). Confirm that the sandbox has the required skills and network permissions:

```python sandbox_skills.py lines theme={null}
import asyncio
import os
from veadk import Agent, Runner
from veadk.tools.builtin_tools.execute_skills import execute_skills

agent = Agent(
    name="sandbox_assistant",
    skills=[os.environ["SKILL_SOURCE_ID"]],
    skills_mode="skills_sandbox",
    tools=[execute_skills],
)

async def main():
    print(await Runner(agent=agent).run(
        messages="List your available skills.", session_id="sandbox-skills"
    ))

if __name__ == "__main__":
    asyncio.run(main())
```

### Dynamic skill loading

`enable_dynamic_load_skills=True` applies to the legacy `Agent.skills` entry. It rereads sources before each turn and updates skill lists, instructions, and tools. The toolset is initialized even if `skills` starts empty. Applications sharing a mutable agent across concurrent calls must manage refresh and invocation concurrency

The flag does not hot-reload local objects passed directly to `SkillToolset(skills=[...])`. Registry-based loading does not require this flag. See [runtime compatibility](https://docs.veadk.xyz/productions/veadk/archives/1.1.26/en/components/agent/runtime.md#runtime-compatibility) for external-runtime restrictions on legacy skill modes


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.