How to Create Custom @skill Decorators with Schema Generation Rules for LLM Agents in DimOS
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, 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 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 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 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.
# 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:
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.
@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
descriptionin the JSON schema. Without it, DimOS raisesValueErrorduring 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
stror 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], andlist[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:
# 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):
{
"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:
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:
pytest dimos/agents/test_agent.py -v
This verifies that get_skills() discovers your method and that agent.py successfully reconstructs the StructuredTool without schema validation errors.
Summary
- Apply
@skillfromdimos/agents/annotation.pyto methods inModulesubclasses to mark them for LLM exposure. - Provide complete type annotations for all parameters so
Module.get_skills()indimos/core/module.pycan generate valid JSON schemas via LangChain'stoolwrapper. - 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →