Self taught skills in OpenClaw
I had my first attempt at getting OpenClaw to turn a relatively basic prompt from me into a reusable skill. It worked, but it wasn’t without issues, so I wanted to dig into how the process worked. My OpenClaw bot also rickrolled me, which I only found out when I checked the contents of the download folder!
The task #
I have a locally running instance of MeTube which I use for making offline copies of videos. Generally speaking I keep my OpenClaw instance extremely isolated in its own Docker container, but I had previously given is access to this specific home server, so I knew it had sufficient access to interact with it.
For my first test of automation, I wanted to see if I could get it to use the service for me. The MeTube web interface is very simple, so it felt like a reasonable challenge.
OpenClaw can generate its own skills, which are persistent capabilities the agent can learn and store for future use.
What I saw #
My primary way of communicating with my OpenClaw while I’m not in the house is via a Matrix client on my phone.
I made an initial request at 17:24 to create the skill, based on some chat history where I outlined the name of the server it should talk to.
14 minutes later it had built the skill. Magic! I wanted to know what went on in the background.
Behind the scenes #
So what did it do for those 14 minutes? After looking into the logs, it followed these steps:
| Tool | Focus | Step thinking |
|---|---|---|
| read | skill-creator/SKILL.md |
First it wants to know how to build a skill, so consults the skill-creator skill |
| exec | curl -s http://home-server.local/metube |
Then it probes the page to find information |
| web_search | metube alexta69 API |
It now understands the name of the tool, and the author, so goes looking |
| web_fetch | github.com/README |
It figures out there’s a bookmarklet, and comprehends how a POST request needs to work |
| exec | mkdir skills/metube |
Now it prepares the file system for the new skill |
| write_file | metube/SKILL.md |
Now it writes the main skill file defining the triggers and natural language usage |
| write_file | metube-add.js |
It generates a small node script to be the main tool entrypoint |
| exec | node metube-add.js |
It then queues “Never Gonna Give You Up” to test |
| process | action: poll |
Because this takes time, it swaps to polling the process for completion |
Deep Dive #
You can find a complete transaction log in ~/.openclaw/agents/main/sessions/, and each of the logfiles is in jsonl format, which is ideal for parsing with jq. I used the following script to help flatten the logfiles into a readable form.
jq -r '. as $msg
| "Time: \($msg.timestamp // "N/A")"
+ (if $msg.message.role == "assistant" and ($msg.message.content[]? | .thinking? // empty) != null
then
"\nAssistant thinking: " + ($msg.message.content[]? | select(.thinking?) | .thinking)
elif $msg.message.role == "toolResult"
then
"\nTool: \($msg.message.toolName // "N/A")"
+ "\nArgs: " + ((($msg.message | .toolCallId?, .toolName?, .content?) | tostring) | .[0:120]) + (if ((($msg.message | .content?) | tostring) | length) > 120 then "..." else "" end)
else ""
end)
+ "\n---"' <filename>
I’ve unpacked a bit more the session logs below, to show you the thinking steps mixed in with the tool calling.
Step 1 #
The user wants me to create a new skill to post YouTube video links to the MeTube service to download videos. Let me first check the skill-creator skill to understand how to create a new skill properly.
read('/app/skills/skill-creator/SKILL.md')
Step 2 #
The user wants a skill that can send YouTube video links to their MeTube service (running at
http://home-server.local/metube) to download videos in the “best” quality.To create this skill, I need to:
- Understand what MeTube is and its API
- Create a skill structure with SKILL.md
- Create a script that can interact with MeTube’s API
Let me first check what MeTube’s API looks like by exploring the meTube endpoint.
exec('curl -s http://home-server.local/metube/')
Step 3 #
The page is Angular-based (app-root with compiled Angular scripts). The metube service likely accepts POST requests to add downloads. Let me check if there’s an API endpoint or if I can find documentation about how metube accepts URLs.
web_search('metube alexta69 API add download POST')
Step 4 #
The user wants a skill to post YouTube video links to MeTube with “best” quality. Let me search for more specific API details and then create the skill.
web_fetch('https://github.com/alexta69/metube/issues/491')
Let me search for more details about MeTube’s API structure
web_fetch('https://github.com/alexta69/metube')
Step 5 #
I found the key information! From the MeTube README, I can see:
- API Endpoint:
POST /add- Example bookmarklet JavaScript that shows exactly how to call the API
I should create this skill using the skill-creator pattern.
exec('cd /home/node/.openclaw/workspace && mkdir -p skills/metube/scripts')
The skill-creator skill says to use init_skill.py to create a new skill, but this script isn’t available. Let me create the skill manually.
write(
filepath='/home/node/.openclaw/workspace/skills/metube/SKILL.md'
contents='---\nname: metube\ndescription: Queue YouTube and other video downloads ...'
)
Step 6 #
Now let me create the Node.js script:
write(
filepath='/home/node/.openclaw/workspace/skills/metube/scripts/metube-add.js'
contents='#!/usr/bin/env node ...'
)
Step 7 #
I should also update the TOOLS.md or similar to document this skill, but first let me check if the script runs and test it against the server. Let me test the script.
exec('cd /home/node/.openclaw/workspace/skills/metube/scripts
&& chmod +x metube-add.js
&& METUBE_URL=\"http://home-server.local/metube\" node metube-add.js \"https://www.youtube.com/watch?v=dQw4w9WgXcQ\"')
The bot specifically chose this URL, in part because I’ve given him a quippy, sarcastic personality.
Conclusion #
So there we have it, a brief peek into the hidden process of skill creation. The results are written to the workspace/skills/ folder, and your new functionality is ready to be called on whenever needed.
I don’t know why it couldn’t see its own init_skill.py script, I can definitely see it in the filesystem inside my Docker container. It’s perhaps some kind of /app vs /home/node path issue.
I did encounter some issues with this process. Namely the lack of feedback during some of the key stages, and a delay because of the skill testing process requiring a full download and transcode. There are also issues with buffered responses in Matrix, where a “What’s taking so long??” message can chain another set of behaviours without the initial success being sent until it’s handled your new request.