# How to Create Custom @skill Decorators with Schema Generation Rules for LLM Agents in DimOS

> Learn to create custom @skill decorators in DimOS for LLM agents. Automatically generate JSON schemas using type annotations and docstrings for seamless integration.

- Repository: [Dimensional/dimos](https://github.com/dimensionalOS/dimos)
- Tags: how-to-guide
- Published: 2026-03-15

---

**To create a custom @skill decorator in DimOS, apply the `@skill` annotation to a type-annotated method with a complete docstring, and the framework automatically generates a JSON schema via LangChain's tool wrapper for LLM agent consumption.**

In the **dimensionalOS/dimos** framework, any method decorated with `@skill` on a `Module` subclass becomes an automatically discoverable tool for LLM-driven agents. When a blueprint initializes, the runtime scans loaded modules, identifies callables marked with the `__skill__` attribute in [`dimos/agents/annotation.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/agents/annotation.py), and generates strict JSON schemas using LangChain's `tool` wrapper and Pydantic models.

## Understanding the @skill Decorator Architecture

The DimOS framework implements a three-stage pipeline for exposing Python methods as LLM-callable tools. Understanding these core components ensures your custom skills integrate correctly with the agent system.

**Stage 1: Decoration.** The `@skill` decorator in [`dimos/agents/annotation.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/agents/annotation.py) marks methods by setting `func.__rpc__ = True` and `func.__skill__ = True` on the function object. This dual-flag system enables both RPC routing and tool discovery without modifying the function signature.

**Stage 2: Schema Generation.** The `Module.get_skills()` method in [`dimos/core/module.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/core/module.py) introspects all class attributes, filters for callables possessing `__skill__`, and invokes `tool(attr).args_schema.model_json_schema()`. This LangChain utility inspects type hints and docstrings to build Pydantic models, then serializes them to JSON schema strings stored in `SkillInfo` objects.

**Stage 3: Tool Construction.** The agent subsystem in [`dimos/agents/agent.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/agents/agent.py) reconstructs LangChain `StructuredTool` instances from the `SkillInfo` objects, binding the JSON schema to the underlying method for runtime execution.

## Step-by-Step: Creating a Custom Skill with Schema Generation

### Step 1: Apply the @skill Decorator

Import the decorator from the annotations module and apply it to any method within a `Module` subclass. The decorator itself performs minimal runtime modification, deferring validation until module initialization.

```python

# dimos/agents/annotation.py

def skill(func: F) -> F:
    func.__rpc__ = True          # enables RPC routing

    func.__skill__ = True        # marks the method for tool generation

    return func

```

In your implementation file, use the decorator alongside the `@rpc` decorator for lifecycle methods:

```python
from dimos.agents.annotation import skill
from dimos.core.module import Module

class CustomMoveSkill(Module):
    @skill
    def move(self, speed: float, duration: float = 1.0) -> str:
        """Method implementation here."""
        pass

```

### Step 2: Define Type Annotations and Docstrings

**LangChain derives the JSON schema directly from Python type hints**, converting them to Pydantic field types. Every parameter must have an explicit type annotation (`str`, `int`, `float`, `bool`, or `list` variants of these).

The **docstring is copied verbatim** into the tool's `description` field for the LLM. Use Google-style or NumPy-style docstrings to describe parameter purposes, as these help the LLM understand argument semantics.

```python
@skill
def move(self, speed: float, duration: float = 1.0) -> str:
    """
    Move the robot at a constant speed.

    Args:
        speed: Linear speed in meters/second. Positive moves forward,
               negative moves backward.
        duration: How many seconds to move. Defaults to 1.0 s.
    """
    return f"Moved at {speed} m/s for {duration}s"

```

Missing type annotations cause the generated schema to omit those arguments entirely, preventing the LLM from supplying values. Missing docstrings raise `ValueError` at module startup.

### Step 3: Return String Responses

The agent expects string responses for text-based interactions. Your `@skill` method must return a `str` (or a specialized artefact object implementing `agent_encode` for image-style outputs). Non-string return types result in generic "It has started..." messages or ignored values.

## Schema Generation Rules for LLM Agents

The `Module.get_skills()` method enforces strict constraints when generating schemas via `tool(attr).args_schema.model_json_schema()`. Follow these rules to ensure successful LLM integration:

- **Docstring must exist.** The docstring becomes the tool's `description` in the JSON schema. Without it, DimOS raises `ValueError` during blueprint startup and omits the skill.

- **All parameters must be type-annotated.** LangChain requires type hints to construct the Pydantic `args_schema`. Unannotated parameters disappear from the schema, making them inaccessible to the LLM.

- **Return type must be `str` or an artefact object.** The agent processes string responses directly. Complex return objects must implement the artefact encoding protocol.

- **Use simple scalar types only.** Stick to `str`, `int`, `float`, `bool`, `list[str]`, and `list[float]`. Complex nested types (`dict`, `Optional`, or custom classes) cause runtime schema generation errors in the current LangChain integration.

## Complete Example: Building a Custom Move Skill

The following implementation demonstrates a valid custom skill following all schema generation rules:

```python

# file: dimos/agents/skills/custom_move.py

from dimos.agents.annotation import skill
from dimos.core.core import rpc
from dimos.core.module import Module

class CustomMoveSkill(Module):
    """Expose basic robot movement as an LLM-callable skill."""

    @rpc
    def start(self) -> None:
        super().start()          # initialise streams, RPC, etc.

    @skill
    def move(self, speed: float, duration: float = 1.0) -> str:
        """
        Move the robot at a constant speed.

        Args:
            speed: Linear speed in meters/second. Positive moves forward,
                   negative moves backward.
            duration: How many seconds to move. Defaults to 1.0 s.
        """
        # Insert robot-specific command here

        print(f"[CustomMoveSkill] speed={speed}, duration={duration}")
        return f"Moved at {speed} m/s for {duration}s"

# Blueprint entry – discovered automatically by `dimos list`

custom_move_skill = CustomMoveSkill.blueprint

```

When the blueprint launches, `Module.get_skills()` generates the following JSON schema (simplified):

```json
{
  "type": "object",
  "properties": {
    "speed": {"type": "number"},
    "duration": {"type": "number", "default": 1.0}
  },
  "required": ["speed"]
}

```

The LLM receives this schema as part of the tool definition, enabling it to generate structured calls like `{"speed": 0.5, "duration": 2}`.

## Integrating Custom Skills into Blueprints

After creating your skill module, expose it to the agent by importing the blueprint variable into your target agent configuration:

```python
from dimos.agents.skills.custom_move import custom_move_skill
from dimos.core.blueprint import autoconnect

my_blueprint = autoconnect(
    robot_modules,
    custom_move_skill,          # added skill module

    agent,
)

```

Validate the integration by running the agent tests:

```bash
pytest dimos/agents/test_agent.py -v

```

This verifies that `get_skills()` discovers your method and that [`agent.py`](https://github.com/dimensionalOS/dimos/blob/main/agent.py) successfully reconstructs the `StructuredTool` without schema validation errors.

## Summary

- **Apply `@skill`** from [`dimos/agents/annotation.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/agents/annotation.py) to methods in `Module` subclasses to mark them for LLM exposure.
- **Provide complete type annotations** for all parameters so `Module.get_skills()` in [`dimos/core/module.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/core/module.py) can generate valid JSON schemas via LangChain's `tool` wrapper.
- **Write detailed docstrings** that become the tool descriptions consumed by the LLM.
- **Return strings** from skill methods to ensure proper agent response handling.
- **Import the skill's blueprint** into your agent configuration file to register it with the runtime.

## Frequently Asked Questions

### What happens if I forget to type-annotate a parameter in a @skill method?

If a parameter lacks a type annotation, LangChain's `tool()` wrapper cannot generate a corresponding field in the Pydantic `args_schema`. Consequently, the parameter disappears from the JSON schema in `SkillInfo`, and the LLM has no mechanism to supply that argument during tool invocation. Always annotate parameters with simple types like `str`, `float`, or `int`.

### Can I use complex nested types like dict or Optional in @skill parameters?

No. The current DimOS implementation in [`dimos/core/module.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/core/module.py) uses LangChain's default schema generator, which supports only simple scalar types (`str`, `int`, `float`, `bool`) and basic list variants (`list[str]`, `list[float]`). Complex nested types, `Optional` wrappers, or custom class annotations cause runtime errors during `model_json_schema()` serialization.

### How does DimOS convert the generated schema into a callable tool for the LLM?

The agent subsystem in [`dimos/agents/agent.py`](https://github.com/dimensionalOS/dimos/blob/main/dimos/agents/agent.py) receives the `SkillInfo` object containing the JSON schema string. It deserializes the schema with `json.loads(skill.args_schema)` and constructs a LangChain `StructuredTool`, binding the schema to a wrapped function that routes calls back to your module method. This `StructuredTool` is then passed to the LLM agent for invocation.

### Where should I place new skill modules in the DimOS codebase?

Create new skill modules under `dimos/agents/skills/` for general capabilities, or within robot-specific packages for hardware-tied functionality. Ensure the file exports a blueprint variable (e.g., `custom_move_skill = CustomMoveSkill.blueprint`) so the `autoconnect` system in your agent configuration can discover and load the module during blueprint initialization.