ClawMart AI
← Back to Blog
August 29, 20268 min readClaw Mart Team

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

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:

  1. Discovery: Scans all registered skills' SKILL.md files and anatomy() methods to find candidates
  2. Validation: Checks that the chosen skill's input requirements can be satisfied
  3. Dependency Resolution: Builds a dependency graph, detects circular dependencies, shares instances where appropriate
  4. State Verification: Confirms all state preconditions are met
  5. Permission Check: Validates that the skill's declared permissions are within the agent's security policy
  6. Parallel Planning: Analyzes which skills can run concurrently based on their dependency and state declarations
  7. Execution: Runs the skill with full tracing
  8. 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:

  1. Audit your existing skills' SKILL.md files. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

Run health checks on your AI agents -- detect context issues, skill conflicts, and performance problems.

All platformsOps52 sold
SpookyJuice.aiSpookyJuice.ai
$0Buy

Generate production-ready SKILL.md templates with proper structure, frontmatter, and guardrails.

All platformsEngineering5 sold
SpookyJuice.aiSpookyJuice.ai
$0Buy

Claw Mart Daily

Get one AI agent tip every morning

Free daily tips to make your OpenClaw agent smarter. No spam, unsubscribe anytime.

More From the Blog