
Building Custom Pilot Skills with OpenClaw
An OpenClaw skill is a folder with a SKILL.md file: YAML frontmatter that says what the skill is for, and Markdown instructions the agent follows. When the Pilot daemon is running, it already installs one skill into OpenClaw for you, a general guide to pilotctl. This article shows how to write your own skill on top of it, one that teaches an agent a specific workflow over Pilot Protocol, and how to publish it on ClawHub.
Anatomy of a Skill
OpenClaw loads a skill from any folder under its skill roots that contains a SKILL.md. Your own skills usually go in the workspace's skills/ folder or in the shared ~/.openclaw/skills. The file has two parts:
- Frontmatter.
nameanddescriptionare required. OpenClaw puts each skill's name and description into the agent's system prompt, so the description is what the agent reads to decide when to use the skill. Say plainly when it applies, and keep it short. Runtime requirements go undermetadata.openclaw:requires.binslists binaries that must be onPATHbefore OpenClaw loads the skill. - Body. Plain Markdown: when to use the skill, the steps, the exact commands, and what to do when something fails.
pilot-code-review/
SKILL.md # required
examples/ # optional supporting files
The skill Pilot installs lives at ~/.openclaw/skills/pilotctl/SKILL.md. The daemon refreshes it every 15 minutes, so do not edit it; your changes would be overwritten. Put your own skills in their own folders. To see where the daemon installs its skill for each agent tool, and whether it is up to date:
pilotctl skills # per-tool install state
pilotctl skills paths # install paths only
Building a Custom Workflow Skill
Suppose you want OpenClaw agents to use Pilot Protocol for distributed code review: one agent sends a file to a reviewer agent and gets findings back. The general pilotctl skill already covers the commands. A custom skill adds the workflow: the message format, the order of steps, and what to do on each error.
---
name: pilot-code-review
description: >
Send a source file to a reviewer agent over Pilot Protocol and collect its
findings, or answer review requests that arrive in the Pilot inbox. Use when
the user asks for another agent to review code, or when a message with
"type": "review_request" is waiting in pilotctl inbox.
version: 1.0.0
metadata:
openclaw:
requires:
bins:
- pilotctl
homepage: https://pilotprotocol.network/docs/
---
# Code review over Pilot Protocol
Messages are JSON.
- Request: {"type": "review_request", "file": "<file name>", "focus": "security|performance|style|all"}
- Result: {"type": "review_result", "file": "<file name>", "severity": "clean|minor|major|critical", "findings": [...]}
## Before you start
1. Check the daemon: `pilotctl --json daemon status`. If it is not running, run `pilotctl daemon start`.
2. You need the reviewer's Pilot address (N:NNNN.HHHH.LLLL). If you only have a
hostname, resolve it: `pilotctl --json find <hostname>`.
3. Check trust: `pilotctl --json trust`. If the reviewer is not listed, run
`pilotctl handshake <address> "code review requests"` and tell the user the
reviewer must approve. Continue only once it appears in `pilotctl trust`.
## Request a review
1. Send the file: `pilotctl --json send-file <address> <path>`
2. Send the request and wait for the answer:
`pilotctl --json send-message <address> --type json --data '{"type":"review_request","file":"<file name>","focus":"security"}' --wait 10m`
3. The answer is in data.reply.data (a JSON string). Parse it and show the findings to the user.
4. If the wait times out, the answer may still arrive later. Check with
`pilotctl --json inbox --from <address> --since 1h --full`. Do not send the file again.
## Answer a review request
1. List messages: `pilotctl --json inbox --full --limit 0`. Pick those whose data has "type": "review_request".
2. Find the file: `pilotctl --json received --since 1h` (files land in ~/.pilot/received/).
3. Review it, then answer the sender (the message's "from") and name the request it answers:
`pilotctl --json send-message <from> --type json --reply-to <message_id> --data '<result JSON>'`
## Errors
Failed commands exit non-zero and print JSON on stderr with "code", "message" and "hint".
- trust_required: run the command in "hint" (a handshake), then wait for the reviewer to approve.
- not_found: the hostname is unknown, or the node is private and does not trust you yet. Ask the user for the address.
- timeout: no answer within the wait. Check the inbox later; do not resend.
- not_running or connection_failed: run `pilotctl daemon start`, then retry once.
The skill wraps a few generic commands, send-file, send-message --wait and inbox, into one task the agent can follow from start to finish. The --reply-to step matters: it ties the result to the request's message ID, which is how the requester's --wait picks out the right answer.
Two things the skill cannot do for you. Trust is per pair of nodes, so a private reviewer has to approve each new requester (or run its daemon with --trust-auto-approve, which approves everyone). And the reviewer only answers when its agent runs a turn and reads the inbox, so give --wait enough time, or have the requester check the inbox later.
Testing It Locally
Before publishing, install the skill by copying the folder into one of OpenClaw's skill roots:
cp -r ./pilot-code-review ~/.openclaw/skills/
openclaw skills check # if it does not show up for the agent
With skills.load.watch on, OpenClaw rescans skill folders when files change, and existing sessions pick up the change on their next turn; restarting the Gateway also refreshes skills. Because the frontmatter lists pilotctl under requires.bins, OpenClaw only loads the skill on hosts where pilotctl is on PATH. It does not install Pilot; install it first with curl -fsSL https://pilotprotocol.network/install.sh | sh.
Then try it the way it will be used: give an agent the task without extra instructions and watch where it gets stuck. Every place it hesitates is a gap in the skill.
Publishing to ClawHub
ClawHub is OpenClaw's skill registry. Log in with the clawhub CLI, then publish the folder that contains SKILL.md:
clawhub login
clawhub skill publish ./pilot-code-review \
--slug pilot-code-review \
--name "Pilot Code Review" \
--version 1.0.0 \
--changelog "First release"
ClawHub keeps semver versions, tags (latest by default) and changelogs; publish again with a new --version to release an update. Publishing needs a GitHub account old enough to pass ClawHub's upload gate. A .clawhubignore file in the folder keeps files out of the bundle.
Anyone can then install it into their OpenClaw workspace:
openclaw skills install @<owner>/pilot-code-review
# or, with the ClawHub CLI
clawhub install @<owner>/pilot-code-review
OpenClaw's own guidance is to treat third-party skills as untrusted code and read them before enabling them. Keep yours short enough to read, and keep secrets out of it. The exact flags are in the ClawHub documentation and the skill format reference.
Error Handling for Autonomous Agents
The most important part of a skill is what it says about failure. An autonomous agent cannot ask a human what to do, so every error the agent can hit needs a clear next step:
- Bad: "If something fails, check the docs." The agent does not know what to do.
- Good: "trust_required: run the command in hint, then wait for the reviewer to approve." The agent knows exactly what to run.
pilotctl helps here. With --json, a failed command exits non-zero and prints an object with a stable code (for example trust_required, not_found, timeout, connection_failed), a message and usually a hint with the command to run next. A skill can key its instructions on those codes instead of on error text.
Good error instructions are:
- Actionable. Say what to do, not just what went wrong.
- Self-contained. Include the full command, not a reference to other docs.
- Clear about retries. Say whether an error is transient (retry once) or permanent (stop and ask the user), and what must not be repeated, such as resending a large file.
Runtime Context Discovery
Beyond the static skill file, an agent can ask pilotctl what it supports:
# Machine-readable metadata for every command
pilotctl --json context
# Just one command
pilotctl context send-message
This returns each command's name, description, argument templates and return field names. Its advantage over a skill file is that it always matches the installed version. A skill can tell the agent to check pilotctl context <command> when a flag it expects is rejected.
Agents can also tell each other what they support, as long as both sides follow the same convention. Your skill can define one, for example a capability_query message that agents with the skill answer with the request and result formats they accept:
# "What can you do?" (the peer must know to answer this)
pilotctl --json send-message <address> --type json --data '{"type":"capability_query"}' --wait 2m
Nothing in the protocol answers that message for you; the agent on the other side does, because its skill tells it to.
Skill Design Best Practices
- Write the description for the agent. It decides when the skill loads. Name the situations and the words a user would use.
- Keep steps small and ordered. Single-purpose steps that the agent can follow and check are better than one long paragraph.
- Use JSON everywhere. Tell the agent to pass
--jsonand say where the answer is (for exampledata.reply.data). Agents parse JSON reliably; they struggle with text tables. - Write out full commands. No shorthand. Agents copy examples verbatim.
- Declare what you need. List
pilotctlunderrequires.binsso the skill does not load where it cannot work. - Test with an agent before publishing. Install the folder locally, give an agent the task with no extra help, and fix the skill wherever it gets stuck.
Build Skills for Agents
Teach agents a workflow over Pilot Protocol with one SKILL.md, then share it on ClawHub.
View on GitHub
