OpenClaw Skill Anatomy: Understanding SKILL.md, Scripts & Assets
OpenClaw Skill Anatomy: Understanding SKILL.md, Scripts & Assets

Let's be honest: most people who start building with OpenClaw hit the same wall within about two hours.
They install the framework. They scaffold their first skill. They get something basic working. And then they open someone else's skill ā maybe one from the community registry, maybe one from a teammate ā and they stare at it like they've been handed a blueprint written in a language they almost speak but don't quite.
The file structure looks unfamiliar. There's a SKILL.md file they've never seen before. Scripts are organized in a way that seems deliberate but isn't immediately obvious. Assets are referenced from places they didn't expect. And suddenly, what felt like forward momentum turns into twenty browser tabs of half-relevant documentation.
I've been there. Most people building with OpenClaw have been there. The good news is that once you understand the anatomy of a skill ā really understand it, not just copy-paste from a tutorial ā everything clicks. You stop guessing. You start building with intention. And debugging goes from a three-hour nightmare to a five-minute trace.
So let's dissect this thing properly.
Why Skill Anatomy Matters More Than You Think
Here's the thing that took me too long to learn: an OpenClaw skill is not just a function you expose to an agent. It's a self-describing, composable unit of infrastructure. The "anatomy" isn't a metaphor the OpenClaw team chose because it sounds cool ā it's a design philosophy that solves real, painful problems.
In most agent frameworks, skills (or "tools" as some call them) are black boxes. You register a function, give it a description string, and pray the LLM calls it correctly. When it doesn't work, you get a cryptic "tool failed" message and zero visibility into what actually happened.
OpenClaw takes the opposite approach. Every skill is required to declare its anatomy ā its inputs, outputs, dependencies, state requirements, permissions, and execution characteristics. This declaration isn't just documentation. It's executable infrastructure that the runtime uses to validate, optimize, and debug your agent's behavior.
Think of it this way: if a traditional tool is a sealed pill capsule, an OpenClaw skill is an x-ray of that capsule showing you every ingredient, dosage, and interaction warning. And the runtime reads that x-ray in real time.
The Three Pillars: SKILL.md, Scripts, and Assets
Every OpenClaw skill is built on three structural components. Miss one and your skill either won't work, won't be discoverable, or won't play nicely with others. Let's break each one down.
SKILL.md: The Blueprint
The SKILL.md file is the single most important file in any OpenClaw skill. If the skill were a person, SKILL.md would be its medical chart ā everything another developer (or the runtime itself) needs to know, laid out in a structured format.
Here's what a well-written SKILL.md looks like:
# WeatherLookup
## Anatomy
- **Interface**: DataRetrieval.Realtime
- **Risk Level**: low
- **Parallelizable**: true
- **Avg Duration**: 2.3s
## Inputs
| Name | Type | Required | Description |
|----------|--------|----------|--------------------------|
| location | string | yes | City name or coordinates |
| units | enum | no | "metric" or "imperial" |
## Outputs
| Name | Type | Description |
|-------------|--------|--------------------------------|
| temperature | float | Current temperature |
| conditions | string | Human-readable conditions |
| forecast | array | Next 5 days |
## Dependencies
- `GeocodingService` (optional, for city name resolution)
- `WeatherAPI` (required)
## State
- **Requires**: none
- **Creates**: `last_weather_check`
- **Modifies**: none
## Permissions
- **Network**: outbound to `api.weather.com`
- **Filesystem**: none
- **Confirmation Required**: none
Let's unpack why each section matters.
The Interface declaration (DataRetrieval.Realtime) tells OpenClaw what kind of skill this is. This isn't a free-form tag ā it's a standardized interface type that enables composition. If you have another skill that declares the same interface, they're swappable. Your agent can discover skills by interface type, which means you can build workflows that are resilient to individual skill failures.
Risk Level and Permissions are where OpenClaw shines compared to other frameworks. Instead of hoping your agent doesn't do something destructive, you declare exactly what the skill can and cannot touch. The runtime enforces these declarations. If your WeatherLookup skill suddenly tries to write to the filesystem, OpenClaw blocks it ā because the anatomy says it shouldn't.
# This is what enforcement looks like at runtime
agent.execute("WeatherLookup", {"location": "NYC"})
# OpenClaw checks:
# ā Network access to api.weather.com ā allowed
# ā Filesystem write to ~/data/cache.json ā NOT in permissions
# ā Blocked with clear error message
State declarations solve one of the most infuriating problems in agentic development: the agent forgetting what it did three steps ago. By declaring what state a skill requires, creates, and modifies, OpenClaw can validate your workflow before it runs. If SkillB requires state that SkillA creates, OpenClaw ensures SkillA runs first ā and if it doesn't, you get a clear pre-execution error instead of a mysterious runtime failure.
I cannot overstate how much time this saves. Before I understood SKILL.md properly, I was spending hours debugging state issues. Now I catch them before a single API call is made.
Scripts: Where the Logic Lives
The scripts directory is where your actual execution logic goes. But OpenClaw has opinions about how this should be organized, and those opinions are good ones.
A typical skill's script structure looks like this:
my-skill/
āāā SKILL.md
āāā scripts/
ā āāā main.py # Primary execution entry point
ā āāā validate.py # Input validation logic
ā āāā transform.py # Output transformation
ā āāā compensate.py # Rollback/cleanup logic
āāā assets/
āāā ...
main.py is your entry point. This is where the core skill logic lives:
from openclaw import Claw
claw = Claw()
@claw.skill
class WeatherLookup:
def anatomy(self):
return {
"inputs": {"location": str, "units": str},
"outputs": {"temperature": float, "conditions": str, "forecast": list},
"dependencies": ["WeatherAPI"],
"execution": {
"parallelizable": True,
"avg_duration": 2.3
}
}
def execute(self, inputs, context):
api = context.dependency("WeatherAPI")
location = inputs["location"]
# If location is a city name, resolve it
if not self._is_coordinates(location):
geo = context.dependency("GeocodingService", optional=True)
if geo:
location = geo.resolve(location)
result = api.get_current(location, units=inputs.get("units", "metric"))
return {
"temperature": result.temp,
"conditions": result.description,
"forecast": result.five_day
}
Notice something important here: the anatomy() method mirrors what's in SKILL.md. This is intentional. SKILL.md is the human-readable declaration; the anatomy() method is the machine-readable one. OpenClaw validates that they match. If you declare an input in SKILL.md but forget it in your code (or vice versa), you'll get a build-time warning. This dual-declaration pattern catches mismatches before they become runtime bugs.
validate.py handles input validation separately from your main logic. This is a pattern I initially resisted ("why not just validate in main.py?") but have come to love:
def validate_inputs(inputs):
if "location" not in inputs:
raise ValidationError("location is required")
if "units" in inputs and inputs["units"] not in ["metric", "imperial"]:
raise ValidationError(f"units must be 'metric' or 'imperial', got '{inputs['units']}'")
return True
Separating validation means OpenClaw can run input checks before spinning up dependencies. If your skill depends on an expensive API connection, you don't want to establish that connection just to discover the inputs were malformed.
compensate.py is where OpenClaw's transactional execution shines. This file defines how to undo what the skill did if a downstream step fails:
def compensate(execution_context):
"""Called when a downstream skill in the workflow fails."""
# For a read-only skill like WeatherLookup, compensation is trivial
# For skills that modify state, this is where you roll back
if execution_context.state_modified("last_weather_check"):
execution_context.revert("last_weather_check")
return {"compensated": True}
For a simple weather lookup, compensation is trivial. But imagine a TripBooking skill that reserves a hotel, books a flight, and charges a card. If the card charge fails, the compensation logic automatically cancels the hotel and flight:
@claw.skill
class TripBooking:
def anatomy(self):
return {
"transaction": {
"type": "compensating",
"steps": [
{"skill": "HotelReservation", "compensate": "cancel_hotel"},
{"skill": "FlightBooking", "compensate": "cancel_flight"},
{"skill": "PaymentProcessor", "compensate": "refund"}
]
},
"failure_modes": {
"payment_failed": "rollback_all",
"hotel_unavailable": "continue",
"api_timeout": "retry[3]"
}
}
This is the kind of infrastructure that takes hundreds of lines of bespoke error-handling code in other frameworks. In OpenClaw, it's a declaration in your anatomy.
Assets: The Support System
The assets/ directory holds everything your skill needs that isn't code ā configuration files, templates, prompt fragments, schema definitions, reference data.
assets/
āāā config.yaml # Runtime configuration
āāā prompts/
ā āāā description.txt # How the LLM should understand this skill
āāā schemas/
ā āāā output.json # JSON schema for output validation
āāā fixtures/
āāā test_data.json # Test fixtures for anatomical testing
The most underrated file in here is prompts/description.txt. This is the natural language description that gets passed to the LLM when it's deciding which skills to use. A bad description means the agent picks the wrong skill. A good one means precise tool selection:
WeatherLookup retrieves current weather conditions and a 5-day forecast
for a given location. Use this when the user asks about weather, temperature,
or outdoor conditions. Do NOT use this for historical weather data ā
use WeatherHistory instead. Accepts city names ("Paris") or coordinates
("48.8566,2.3522").
The fixtures/test_data.json file enables what OpenClaw calls "anatomical testing" ā testing your skill's structure and behavior without making real API calls:
def test_weather_lookup_anatomy():
skill = WeatherLookup()
# Test the anatomy declaration
assert skill.anatomy()["inputs"]["location"] == str
assert skill.anatomy()["execution"]["parallelizable"] is True
# Test with mock data from fixtures
result = skill.execute_with_anatomy(
inputs={"location": "NYC", "units": "metric"},
mock_dependencies={
"WeatherAPI": MockWeatherAPI(fixture="assets/fixtures/test_data.json")
}
)
assert result["temperature"] is not None
assert isinstance(result["forecast"], list)
assert len(result["forecast"]) == 5
No LLM calls. No API costs. Just pure structural validation. I run these tests on every commit and they catch 80% of issues before I ever deploy.
How It All Connects: The Runtime Perspective
When your agent receives a request, here's what OpenClaw actually does with all this anatomical information:
- Discovery: Scans all registered skills'
SKILL.mdfiles andanatomy()methods to find candidates - Validation: Checks that the chosen skill's input requirements can be satisfied
- Dependency Resolution: Builds a dependency graph, detects circular dependencies, shares instances where appropriate
- State Verification: Confirms all state preconditions are met
- Permission Check: Validates that the skill's declared permissions are within the agent's security policy
- Parallel Planning: Analyzes which skills can run concurrently based on their dependency and state declarations
- Execution: Runs the skill with full tracing
- Compensation: If anything fails, rolls back using the declared compensation logic
You can see all of this happening in real time:
āā Agent Execution Trace āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Request: "What's the weather in Paris?" ā
ā ā
ā Discovery: 3 candidates ā
ā āā WeatherLookup (score: 0.95) ā
ā āā WeatherHistory (score: 0.31) ā
ā āā NewsSearch (score: 0.12) ā
ā ā
ā Selected: WeatherLookup ā
ā Dependencies: WeatherAPI ā, GeocodingService ā ā
ā Permissions: network(api.weather.com) ā ā
ā State: no preconditions ā
ā ā
ā Execution: ā
ā āā GeocodingService.resolve("Paris") ā 48.85,2.35 ā
ā āā WeatherAPI.get_current(48.85,2.35) ā 200 OK ā
ā āā Output: {temp: 18.5, conditions: "Partly..."} ā
ā ā
ā Duration: 2.1s | Cost: $0.003 ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
This level of visibility is what makes OpenClaw fundamentally different. You're never guessing. You're never wondering which skill failed or why. The anatomy makes everything observable.
The Composition Payoff
Once your skills have proper anatomy, composition becomes almost trivial. Skills that share the same interface are automatically interchangeable:
# Both implement Communication.Async interface
agent.add_skill(EmailSkill())
agent.add_skill(SlackSkill())
# Agent can use either one based on context
agent.find_skills_providing("send_message")
# ā [EmailSkill, SlackSkill]
# Swap implementations without changing workflows
agent.replace_skill("EmailSkill", "SlackSkill")
# All workflows that used EmailSkill now use SlackSkill
# Because they share the same anatomical interface
This is the kind of thing that sounds simple in a blog post but is genuinely transformative in practice. I've swapped out entire skill implementations in production without touching a single workflow definition.
The Fastest Way to Get This Right
Here's my honest recommendation: don't try to learn all of this by building from scratch on day one.
I wasted my first week writing malformed SKILL.md files and getting the anatomy declarations subtly wrong. The feedback loop was slow because I didn't yet know what "right" looked like.
If you want to shortcut that learning curve, Felix's OpenClaw Starter Pack on Claw Mart is genuinely the best $29 I've spent on tooling in the past year. It includes a set of pre-configured skills with properly structured SKILL.md files, well-organized scripts, complete asset directories, and ā crucially ā working anatomical tests you can study and modify. Instead of guessing at the right structure, you can read a working example, understand why each piece exists, and then adapt it for your own skills. It's how I actually learned the patterns I've described in this post.
You could absolutely piece this together yourself from the OpenClaw docs and community forums. But having a reference implementation that you know is correct saves a remarkable amount of frustration.
What to Do Next
If you've been building with OpenClaw and your skills feel fragile or hard to debug, here's your action plan:
-
Audit your existing skills'
SKILL.mdfiles. Are they complete? Do they declare all inputs, outputs, dependencies, state requirements, and permissions? If not, fill in the gaps. You'll immediately start catching issues earlier. -
Separate your validation and compensation logic into dedicated scripts. Even if your skills are simple today, this separation pays off the moment you start composing workflows.
-
Add anatomical tests. Start with structural tests (does the anatomy declaration match reality?) and then add fixture-based execution tests. These are free to run and catch the majority of issues.
-
Use
claw.visualize_skill_anatomy()on your most complex skills. Seeing the dependency graph and state flow visually will reveal problems you didn't know you had. -
Declare interfaces on every skill. Even if you only have one implementation today, the interface declaration makes future composition seamless.
The anatomy pattern is OpenClaw's core insight: skills that describe themselves are skills that compose, debug, and scale without drowning you in glue code. Once you internalize this, you stop fighting the framework and start building things that actually work.
Recommended for this post


