M
MeshWorld.
OpenClaw Agent Skills Agentic AI Tutorial Tools Self-Hosted 8 min read

Build Your First Agent Skill for OpenClaw (Step-by-Step)

By Vishnu Damwala

My OpenClaw agent was useful for conversations. Then I added my first skill and it went from “a chatbot” to “something that actually does things.”

This guide walks you through building a custom OpenClaw skill from scratch. By the end you’ll have a working skill that your agent can call on demand.


What you need

  • OpenClaw installed (openclaw --version should show v2026.3.8 or later)
  • At least one agent already set up (follow the first agent tutorial if you haven’t)
  • Node.js (any version ≥18)

How OpenClaw skills work

A skill in OpenClaw is a directory inside ~/.openclaw/skills/ that contains:

~/.openclaw/skills/
└── my-skill/
    ├── SKILL.md        ← instructions + metadata (required)
    └── scripts/        ← helper scripts (optional)
        └── run.js

SKILL.md is the heart of the skill. It has a YAML frontmatter block that defines the skill’s name, description, and settings — then a Markdown body that gives the agent instructions on how to use it.

That’s it. No framework, no npm install, no build step. Just a folder and a file.


Skill #1 — A simple weather lookup

Let’s start small. A skill that fetches current weather for a city.

Create the skill directory:

mkdir -p ~/.openclaw/skills/get-weather/scripts

Create the SKILL.md:

touch ~/.openclaw/skills/get-weather/SKILL.md

Paste this into ~/.openclaw/skills/get-weather/SKILL.md:

---
name: get-weather
description: >
  Get current weather conditions for any city.
  Use this when the user asks about weather, temperature,
  rain, or whether they need an umbrella or jacket.
user-invocable: false
---

## How to use this skill

Call the get-weather script with a city name. It returns current
temperature, conditions, and humidity.

## When to call it

- User mentions weather, temperature, or climate for a location
- User asks if they should pack for rain
- User asks what to wear

## Input

city: string — the city name (e.g. "Mumbai", "London")

## Output

JSON with: temp (°C), condition (string), humidity (%)

## Call

/skill get-weather --city "<city name>"

Now create the actual script that fetches weather. We’ll use the Open-Meteo API — it’s free, no key needed.

Create ~/.openclaw/skills/get-weather/scripts/run.js:

#!/usr/bin/env node

// Parse --city flag from args
const args = process.argv.slice(2);
const cityIndex = args.indexOf("--city");
const city = cityIndex !== -1 ? args[cityIndex + 1] : null;

if (!city) {
  console.error(JSON.stringify({ error: "Missing --city argument" }));
  process.exit(1);
}

async function getWeather(cityName) {
  // Step 1: geocode city name to lat/lon
  const geoRes = await fetch(
    `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1`
  );
  const geoData = await geoRes.json();

  if (!geoData.results?.length) {
    return { error: `City not found: ${cityName}` };
  }

  const { latitude, longitude, name, country } = geoData.results[0];

  // Step 2: fetch current weather
  const weatherRes = await fetch(
    `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&hourly=relativehumidity_2m`
  );
  const weatherData = await weatherRes.json();

  const current = weatherData.current_weather;
  const humidity = weatherData.hourly.relativehumidity_2m[0];

  // WMO weather code → human description
  const conditions = {
    0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
    45: "Foggy", 61: "Light rain", 63: "Moderate rain", 65: "Heavy rain",
    80: "Rain showers", 95: "Thunderstorm"
  };

  return {
    city: `${name}, ${country}`,
    temp: `${current.temperature}°C`,
    condition: conditions[current.weathercode] ?? `Code ${current.weathercode}`,
    humidity: `${humidity}%`,
    wind: `${current.windspeed} km/h`
  };
}

getWeather(city)
  .then(result => console.log(JSON.stringify(result, null, 2)))
  .catch(err => console.error(JSON.stringify({ error: err.message })));

Make it executable:

chmod +x ~/.openclaw/skills/get-weather/scripts/run.js

Test it manually first

Before trusting your agent to use the skill, verify it works:

node ~/.openclaw/skills/get-weather/scripts/run.js --city "Mumbai"

Expected output:

{
  "city": "Mumbai, India",
  "temp": "31°C",
  "condition": "Partly cloudy",
  "humidity": "78%",
  "wind": "14 km/h"
}

Tell the agent about the skill

Open your agent’s config.yaml and add the skill:

name: myagent
llm:
  provider: claude
  model: claude-sonnet-4-6

skills:
  - get-weather

memory:
  dir: ~/.openclaw/agents/myagent/memory/

Restart the agent:

openclaw restart myagent

Now talk to your agent:

You:   Will it rain in Delhi tomorrow?

Agent: Let me check the current conditions in Delhi.
       [calls get-weather --city "Delhi"]
       Delhi is currently showing light rain at 27°C with 82% humidity.
       Rain is likely tomorrow — I'd bring an umbrella.

The agent didn’t hallucinate a weather report. It fetched real data.


Skill #2 — A user-invocable command skill

The previous skill was user-invocable: false — the agent decides when to use it. Sometimes you want a skill you can trigger directly with a slash command.

Let’s build /standup — a skill that generates your daily standup notes from your memory files.

Create the skill:

mkdir ~/.openclaw/skills/standup

~/.openclaw/skills/standup/SKILL.md:

---
name: standup
description: >
  Generate a formatted daily standup summary from the agent's
  memory files. Lists what was done yesterday, what's planned
  today, and any blockers.
user-invocable: true
---

## What this skill does

When the user runs /standup, read the agent memory directory
and generate a standup in this exact format:

**Yesterday:**
- [list tasks marked done in the last 24h from memory]

**Today:**
- [list tasks marked as in-progress or planned]

**Blockers:**
- [list anything flagged as blocked, or "None" if clean]

Keep it concise — standup should take 2 minutes to read, not 20.

## Memory location

Read from the agent's memory directory. Look for files containing
task lists, done items, or progress notes.

Now when you type /standup in the OpenClaw TUI or messaging interface, the agent reads your memory files and formats a standup in seconds.


Skill #3 — A skill with environment variable gating

Some skills only work if the right environment variables are set. Use the metadata block to gate skill availability.

This skill queries a local SQLite database — it should only activate if the database file path is configured:

---
name: query-db
description: >
  Run a read-only SQL query against the local project database.
  Use this when the user asks about data, records, counts, or
  wants to look something up in the database.
user-invocable: false
metadata:
  openclaw:
    requires:
      env: ["PROJECT_DB_PATH"]
---

## How to use this skill

Call the query-db script with a SQL query (SELECT only).

## Input

query: string — a SELECT SQL statement

## Safety

Only run SELECT queries. Refuse any INSERT, UPDATE, DELETE, DROP,
or CREATE statements. Return an error if a non-SELECT query is passed.

## Call

/skill query-db --query "<SQL statement>"

If PROJECT_DB_PATH is not set in the environment, OpenClaw automatically hides this skill from the agent — the agent won’t try to call it and fail.

Set it when you want the skill active:

export PROJECT_DB_PATH="/home/vishnu/projects/myapp/data.sqlite"
openclaw restart myagent

The SKILL.md fields explained

FieldRequiredWhat it does
nameYesUnique identifier. Used in skills: config and slash commands
descriptionYesWhat the AI reads to decide when to call the skill. Write this carefully.
user-invocableNo (default: false)true = available as /skillname slash command
disable-model-invocationNotrue = the AI can never auto-call this, only manual invocation
homepageNoLink to documentation or repo
metadata.openclaw.requires.binsNoSystem binaries that must exist (e.g. ["ffmpeg"])
metadata.openclaw.requires.envNoEnv vars that must be set
metadata.openclaw.requires.configNoConfig paths that must exist
metadata.openclaw.osNoLimit to specific OS: ["darwin", "linux", "win32"]

Description writing tips — this is the most important part

The AI decides which skill to call based entirely on the description field. A vague description = wrong skill calls. A precise description = the agent makes the right choice every time.

Too vague:

description: Gets information

Too broad:

description: Use this for anything related to the internet

Just right:

description: >
  Get current weather conditions and temperature for a city.
  Use this when the user asks about weather, temperature, rain,
  forecasts, or what to wear. Do NOT use this for historical
  climate data.

Include: what the skill returns, when to use it, and when NOT to use it.


Troubleshooting

Agent doesn’t seem to know about the skill

  • Check skills: is in config.yaml with the correct skill name
  • Restart the agent: openclaw restart <agentname>
  • Confirm the skill directory name matches the name in SKILL.md

Skill runs but returns wrong data

  • Test the script directly in terminal before blaming the agent
  • Add console.error() logging to your script for debugging

Agent calls the wrong skill

  • Improve the description field — make it more specific
  • Add “Do NOT use this for X” to prevent overlap with other skills

Skill not available on this OS

  • Check your metadata.openclaw.os field
  • If using requires.bins, run which <binary> to verify it’s installed

What’s next

Understand skill concepts: What Are Agent Skills? AI Tools Explained Simply

Add memory to your agent: Agent Skills with Memory: Persisting State Between Chats

Build a real-world GitHub issues skill: Build a GitHub Issue Creator Skill for Your AI Agent — env gating pattern applied to a production skill

Skills in the Claude API: Agent Skills with the Claude APItool_use in Node.js from scratch

Skills in OpenAI: Agent Skills with the OpenAI API — function calling with gpt-4o