---
sidebar_position: 32
---

# Skills

:::note
The Skills API is experimental. APIs and behavior may still change in future releases.
:::

Skills is a mechanism for equipping an LLM with reusable, self-contained behavioral instructions.
A skill bundles a name, a short description, and a body of instructions (its _content_),
together with optional resources (e.g., references, assets, templates, etc.).
The LLM loads a skill on demand, keeping the initial context small and only pulling in
the detailed instructions when they are actually needed.

:::note
Skills are designed according to the [Agent Skills specification](https://agentskills.io).
:::

## Creating Skills

### From the File System

Typically, each skill lives in its own directory containing a `SKILL.md` file.
The file must start with a YAML front matter block that declares the skill's `name` and `description`.
Everything below the front matter becomes the skill's content — the instructions given to the LLM
when it activates the skill.

```
skills/
├── docx/
│   ├── SKILL.md
│   └── references/
│       └── tracked-changes.md   ← loaded as a resource
└── data-analysis/
    └── SKILL.md
```

Example `SKILL.md`:

```markdown
---
name: docx
description: Edit and review Word documents using tracked changes
---

When the user asks you to edit a Word document:

1. Always use tracked changes so edits can be reviewed.
   ...
```

Any file in the skill directory (other than `SKILL.md` itself and files under a `scripts/`
subdirectory) is automatically loaded as a `SkillResource` that the LLM can read on demand.

Use `FileSystemSkillLoader` from the `langchain4j-skills` module to load skills from the file system:

```xml
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-skills</artifactId>
    <version>1.11.7-beta19</version>
</dependency>
```

```java
// Load all skills found in immediate subdirectories:
List<FileSystemSkill> skills = FileSystemSkillLoader.loadSkills(Path.of("skills/"));

// Or load a single skill by its directory:
FileSystemSkill skill = FileSystemSkillLoader.loadSkill(Path.of("skills/docx"));
```

### From the Classpath

`ClassPathSkillLoader` works like `FileSystemSkillLoader` but resolves skill directories from
the classpath instead of the filesystem. This is useful when skills are bundled inside your
JAR or located under `src/main/resources`:

```
src/main/resources/
└── skills/
    ├── docx/
    │   ├── SKILL.md
    │   └── references/
    │       └── tracked-changes.md
    └── data-analysis/
        └── SKILL.md
```

```java
// Load all skills from a classpath directory:
List<FileSystemSkill> skills = ClassPathSkillLoader.loadSkills("skills");

// Or load a single skill:
FileSystemSkill skill = ClassPathSkillLoader.loadSkill("skills/docx");
```

By default, `ClassPathSkillLoader` uses the thread's context class loader.
You can pass a custom `ClassLoader` if needed:

```java
FileSystemSkill skill = ClassPathSkillLoader.loadSkill("skills/docx", myClassLoader);
```

The same `SKILL.md` format, resource loading rules, and `scripts/` exclusion apply
as with `FileSystemSkillLoader`.

### Programmatically

Skills do not have to be file-system based.
You can create them from any source — a database, a remote API, generated at runtime — using the builder API:

```java
Skill skill = Skill.builder()
        .name("incident-response")
        .description("Step-by-step runbook for diagnosing and resolving production incidents")
        .content("""
                When a production alert fires:
                1. Call `fetchRecentLogs(serviceName)` to retrieve the last 5 minutes of logs.
                2. Call `checkServiceHealth(serviceName)` to get current health metrics.
                3. Based on the findings, call `createIncidentTicket(summary, severity)`.
                4. If severity is CRITICAL, also call `pageOnCall(incidentId)`.
                """)
        .build();
```

You can also attach resources programmatically:

```java
SkillResource reference = SkillResource.builder()
        .relativePath("references/tone-guide.md")
        .content("Use warm, concise language. Avoid jargon.")
        .build();

Skill skill = Skill.builder()
        .name("customer-support")
        .description("Handles customer support inquiries")
        .content("Follow the tone guide in references/tone-guide.md ...")
        .resources(List.of(reference))
        .build();
```

## Modes

Skills can be integrated with an AI Service in two distinct modes, depending on how much
control and trust you need.

### Tool Mode (Recommended)

**Class:** `Skills` (from the `langchain4j-skills` module)

This corresponds to the **Tool-based agents** integration approach described in the
[Agent Skills specification](https://agentskills.io/integrate-skills).

In this mode, the LLM activates a skill to receive step-by-step instructions, then carries
them out by calling the [tools](/tutorials/tools) you have explicitly registered.
**The LLM has no access to the file system at inference time** — all skill content and
resources are loaded into memory upfront (e.g. via `FileSystemSkillLoader`), and the `activate_skill`
and `read_skill_resource` tools returns that preloaded content rather than reading from disk.
Because only your pre-defined tools can be invoked, **there is no risk of arbitrary code execution**.

#### Registered Tools

| Tool                  | When registered                                                                               |
|-----------------------|-----------------------------------------------------------------------------------------------|
| `activate_skill`      | Always. The LLM calls this to load a skill's full instructions into the context.              |
| `read_skill_resource` | When at least one skill has resources. The LLM calls this to read individual reference files. |
| Skill-scoped tools    | After the skill is activated.                                                                 |

#### How It Works

1. The system message lists the available skills (names and descriptions) so the LLM can choose.
2. The user asks a question that requires a specific skill.
3. The LLM calls `activate_skill("my-skill")` to receive its instructions.
4. The LLM follows those instructions to complete the task, optionally reading resource files along the way.

#### Example Skill

Skills describe the _policy_ — the exact order of calls, required arguments, error-handling steps,
and worked examples — while the actual execution stays in type-safe, tested Java code:

```markdown
---
name: process-order
description: Processes a customer order end-to-end
---

To process an order:

1. Call `validateOrder(orderId)` to check the order is valid.
2. Call `reserveInventory(orderId)` to reserve the required stock.
3. Only if reservation succeeds, call `chargePayment(orderId)`.
4. Finally, call `sendConfirmationEmail(orderId)`.

If any step fails, call `rollbackOrder(orderId)` before reporting the error.
```

#### Wiring It Up

Pass the `ToolProvider` from `Skills` to your AI Service builder alongside your regular tools.
Use `formatAvailableSkills()` to inject the skill catalogue into the system message so
the LLM knows which skills it can activate:

```java
Skills skills = Skills.from(FileSystemSkillLoader.loadSkills(Path.of("skills/")));

MyAiService service = AiServices.builder(MyAiService.class)
        .chatModel(chatModel)
        .tools(new OrderTools()) // your tools
        .toolProvider(skills.toolProvider()) // or .toolProviders(myToolProvider, skills.toolProvider()) if you already have a tool provider configured
        .systemMessage("You have access to the following skills:\n" + skills.formatAvailableSkills()
                + "\nWhen the user's request relates to one of these skills, activate it first using the `activate_skill` tool before proceeding.")
        .build();
```

`formatAvailableSkills()` returns an XML-formatted block listing each skill's name and description:

```xml

<available_skills>
    <skill>
        <name>process-order</name>
        <description>Processes a customer order end-to-end</description>
    </skill>
    <skill>
        <name>data-analysis</name>
        <description>Analyse tabular data and produce charts</description>
    </skill>
</available_skills>
```

#### Customisation

The name, description, and parameter metadata of each tool can be overridden through the
corresponding config class on the builder:

```java
Skills skills = Skills.builder()
        .skills(mySkills)
        .activateSkillToolConfig(ActivateSkillToolConfig.builder()
                .name(...)                    // tool name (default: "activate_skill")
                .description(...)             // tool description
                .parameterName(...)           // parameter name (default: "skill_name")
                .parameterDescription(...)    // parameter description
                .throwToolArgumentsExceptions(...) // throw ToolArgumentsException instead of ToolExecutionException (default: false)
                .build())
        .readResourceToolConfig(ReadResourceToolConfig.builder()
                .name(...)                              // tool name (default: "read_skill_resource")
                .description(...)                       // tool description
                .skillNameParameterName(...)             // skill_name parameter name (default: "skill_name")
                .skillNameParameterDescription(...)      // skill_name parameter description
                .relativePathParameterName(...)          // relative_path parameter name (default: "relative_path")
                .relativePathParameterDescription(...)   // static description (takes precedence over provider)
                .relativePathParameterDescriptionProvider(...) // dynamic description based on available resources
                .throwToolArgumentsExceptions(...)       // throw ToolArgumentsException instead of ToolExecutionException (default: false)
                .build())
        .build();
```

#### Skill-Scoped Tools

You can attach tools directly to a skill. These tools are **only exposed to the LLM
after the skill has been activated** via the `activate_skill` tool.
This keeps the LLM's tool list small and focused, and ensures skill-specific tools only
appear when they are relevant.

##### Using `@Tool`-Annotated Methods

The simplest way to attach tools is to pass objects with `@Tool`-annotated methods:

```java
class OrderTools {

    @Tool("Validates a customer order by ID")
    String validateOrder(String orderId) {
        // validation logic
        return "valid";
    }

    @Tool("Charges payment for a customer order")
    String chargePayment(String orderId) {
        // payment logic
        return "charged";
    }
}

Skill skill = Skill.builder()
        .name("process-order")
        .description("Processes a customer order end-to-end")
        .content("""
                To process an order:
                1. Call `validateOrder(orderId)` to check the order is valid.
                2. Call `chargePayment(orderId)`.
                """)
        .tools(new OrderTools())
        .build();
```

Tools can also be attached to an already-built skill using `toBuilder()` — for example,
to add tools to a skill loaded from the file system:

```java
FileSystemSkill skill = FileSystemSkillLoader.loadSkill(Path.of("skills/process-order"));

Skill skillWithTools = skill.toBuilder()
        .tools(new OrderTools())
        .build();
```

##### Using Tool Providers

You can also attach `ToolProvider`s to a skill — for example, to expose tools from an
MCP server only after the skill is activated:

```java
ToolProvider mcpToolProvider = McpToolProvider.builder()
        .mcpClients(mcpClient)
        .toolFilter((tool, mcpClient) -> tool.name().startsWith("inventory_"))
        .build();

Skill skill = Skill.builder()
        .name("inventory-management")
        .description("Manages warehouse inventory")
        .content("""
                Use inventory tools to check stock levels and update quantities.
                """)
        .toolProviders(mcpToolProvider)
        .build();
```

##### Using a `Map<ToolSpecification, ToolExecutor>`

For full control over tool specifications and execution logic, you can pass a map directly:

```java
ToolSpecification validateOrder = ToolSpecification.builder()
        .name("validateOrder")
        .description("Validates a customer order by ID")
        .addParameter("orderId", JsonSchemaProperty.STRING, JsonSchemaProperty.description("The order ID"))
        .build();

ToolExecutor validateOrderExecutor = (request, memoryId) -> {
    String orderId = parseOrderId(request.arguments());
    return validate(orderId);
};

Skill skill = Skill.builder()
        .name("process-order")
        .description("Processes a customer order end-to-end")
        .content("""
                To process an order:
                1. Call `validateOrder(orderId)` to check the order is valid.
                """)
        .tools(Map.of(validateOrder, validateOrderExecutor))
        .build();
```

All three approaches can be combined — `@Tool` methods, `ToolProvider`s, and `Map` entries
are merged into a single set of skill-scoped tools:

```java
Skill skill = Skill.builder()
        .name("process-order")
        .description("Processes a customer order end-to-end")
        .content("...")
        .tools(new OrderTools())
        .tools(Map.of(validateOrder, validateOrderExecutor))
        .toolProviders(mcpToolProvider)
        .build();
```

##### Wiring It Up

```java
Skills skills = Skills.from(skill);

MyAiService service = AiServices.builder(MyAiService.class)
        .chatModel(chatModel)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(100))
        .toolProvider(skills.toolProvider())
        .systemMessage("You have access to the following skills:\n" + skills.formatAvailableSkills()
                + "\nWhen the user's request relates to one of these skills, activate it first.")
        .build();
```

##### How Skill-Scoped Tools Work

1. Before skill activation, the LLM only sees the `activate_skill` (and `read_skill_resource`) tools.
   Skill-scoped tools are not included in the tool list.
2. When the LLM calls `activate_skill("process-order")`, the activation is recorded in the `ToolExecutionResultMessage`.
3. Before the next LLM call (within the same AI Service invocation), the AI Service re-evaluates dynamic tool providers
   against the current messages. The skill-scoped tools (e.g. `validateOrder`) become
   visible and the LLM can call them immediately, in the same AI Service invocation.
   The skill-scoped tools stay visible to the LLM in the next AI Service invocations, they become invisible only when
   the skill is deactivated.

##### Using Skills with Tool Search

Skills work alongside [Tool Search](/tutorials/tools#tool-search). When both are configured,
they operate independently:

- **Skill-scoped tools are never searchable.** They don't appear in the searchable tool pool
  and cannot be found via `tool_search_tool`. They only become visible after the LLM activates
  the corresponding skill.
- **Regular tools remain searchable.** Tools registered via `.tools(...)` on the AI Service
  (not on a skill) continue to be searchable, regardless of whether any skill is activated.
- **`activate_skill` is always visible.** It is marked as `ALWAYS_VISIBLE`, so the LLM can
  always call it even when Tool Search is enabled.

```java
Skills skills = Skills.from(mySkills);

MyAiService service = AiServices.builder(MyAiService.class)
        .chatModel(chatModel)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(100))
        .tools(new MySearchableTools()) // these are searchable
        .toolProvider(skills.toolProvider()) // skill-scoped tools are NOT searchable
        .toolSearchStrategy(new SimpleToolSearchStrategy())
        .systemMessage("You have access to the following skills:\n" + skills.formatAvailableSkills()
                + "\nWhen the user's request relates to one of these skills, activate it first.")
        .build();
```

### Shell Mode (Experimental)

**Class:** `ShellSkills` (from the `langchain4j-experimental-skills-shell` module)

This corresponds to the **Filesystem-based agents** integration approach described in the
[Agent Skills specification](https://agentskills.io/integrate-skills).

:::warning
**Shell execution is inherently unsafe.**
Commands run directly in the host process environment **without any sandboxing, containerization,
or privilege restriction**. A misbehaving or prompt-injected LLM can execute arbitrary commands
on the machine running your application.
Only use this in controlled environments where you fully trust the input and accept
the associated risks.
:::

In this mode, the LLM is given a single `run_shell_command` tool and reads skill instructions
directly from the file system using shell commands. There is no `activate_skill` or
`read_skill_resource` tool — the LLM navigates skill files like a human developer would.

#### Registered Tools

| Tool                | When registered                                                                                   |
|---------------------|---------------------------------------------------------------------------------------------------|
| `run_shell_command` | Always. The LLM runs shell commands to read `SKILL.md` files, resource files and execute scripts. |

#### How It Works

1. The system message lists available skills with their absolute filesystem paths.
2. The user asks a question that requires a specific skill.
3. The LLM runs `cat /path/to/skills/docx/SKILL.md` to read the instructions.
4. The LLM follows those instructions by running further shell commands.

#### Dependency

Shell execution lives in a separate experimental artifact — add it to your build:

```xml

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-experimental-skills-shell</artifactId>
    <version>1.11.7-beta19</version>
</dependency>
```

#### Wiring It Up

All skills must be filesystem-based (loaded via `FileSystemSkillLoader`).
Use `ShellSkills` instead of `Skills`:

```java
ShellSkills skills = ShellSkills.from(FileSystemSkillLoader.loadSkills(Path.of("skills/")));

MyAiService service = AiServices.builder(MyAiService.class)
        .chatModel(chatModel)
        .toolProvider(skills.toolProvider()) // or .toolProviders(myToolProvider, skills.toolProvider()) if you already have a tool provider configured
        .systemMessage("You have access to the following skills:\n" + skills.formatAvailableSkills()
                + "\nWhen the user's request relates to one of these skills, read its SKILL.md before proceeding.")
        .build();
```

`formatAvailableSkills()` includes a `<location>` field so the LLM knows
exactly where to find each `SKILL.md`:

```xml

<available_skills>
    <skill>
        <name>docx</name>
        <description>Edit and review Word documents using tracked changes</description>
        <location>/path/to/skills/docx/SKILL.md</location>
    </skill>
    <skill>
        <name>data-analysis</name>
        <description>Analyse tabular data and produce charts</description>
        <location>/path/to/skills/data-analysis/SKILL.md</location>
    </skill>
</available_skills>
```

#### When to Use Shell Mode

This mode is best suited for **experimentation and prototyping**, or when you want to use
third-party skills published by the community (e.g. from the
[agentskills.io](https://agentskills.io) ecosystem) without first porting them to Java.
It lets you wire up a working workflow quickly, then migrate individual actions
to tools as the solution matures.

#### Customisation

Use `RunShellCommandToolConfig` to tune the working directory, output limits,
and parameter names:

```java
ShellSkills skills = ShellSkills.builder()
        .skills(mySkills)
        .runShellCommandToolConfig(RunShellCommandToolConfig.builder()
                .name(...)                              // tool name (default: "run_shell_command")
                .description(...)                       // tool description (default: includes OS name)
                .commandParameterName(...)              // command parameter name (default: "command")
                .commandParameterDescription(...)       // command parameter description
                .timeoutSecondsParameterName(...)       // timeout parameter name (default: "timeout_seconds")
                .timeoutSecondsParameterDescription(...) // timeout parameter description
                .workingDirectory(...)                  // working directory for commands (default: JVM's user.dir)
                .maxStdOutChars(...)                    // max stdout chars in result (default: 10_000)
                .maxStdErrChars(...)                    // max stderr chars in result (default: 10_000)
                .executorService(...)                   // ExecutorService for reading stdout/stderr streams
                .throwToolArgumentsExceptions(...)      // throw ToolArgumentsException instead of ToolExecutionException (default: false)
                .build())
        .build();
```
