Skill anatomy

ExplanationUpdated 4 min read
View as Markdown
On this page

A skill is a folder with two files that matter. Everything else is optional.

my-skill/
├── SKILL.md          ← what the agent reads: when and how to use the skill
└── client/
    └── schema.yaml   ← the API contract: methods, parameters, auth, guidance

To see a real one, install any published skill and read it (see Use skills in your AI tool), or read a package's methods from the registry:

curl -s "https://market.opvs.ai/api/v1/registry/packages/@opvs-ai/agentboard" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(list(d['methods_index']['agentboard'])[:10])"

SKILL.md: the agent-facing page

The agent loads this file when the skill is in scope. Keep it short: a router, not a manual.

---
name: my-skill
version: "1.0.0"
description: >
  One sentence on what this skill does, naming the words an agent would use for the job.
category: utility
requires_auth: true
requires_brand: true
triggers:
  - my-skill
  - a word the agent might think of
metadata:
  openclaw:
    primaryEnv: OPVS_PAT
---
  • name: kebab-case, the same as the folder. No dots, @ or /.

  • description: the trigger. Agents match on it, so list the verbs and nouns of the job.

  • category: free text. Look at what similar packages use (data-collection, media, communication, integration, utility, …) so workspaces find you under the same heading.

  • triggers: extra words that help discovery.

  • primaryEnv: documentation only. Do not add requires.env for variables another skill provides; see Preflight and common mistakes.

Below the frontmatter, write plain Markdown: a one-line tagline, pointers to sibling skills ("for X use that skill instead"), and one line per method.

schema.yaml: the API contract

One schema.yaml drives everything the platform generates for your skill: typed agent tools, MCP tool definitions, an SDK module, CLI commands and an OpenAPI 3.1 document.

name: my-skill
version: "1.0.0"
description: One sentence (same as SKILL.md).
category: utility
base_url: /api/v1/my-skill
auth:
  type: pat
  header: Authorization
  prefix: Bearer
  env_var: OPVS_PAT
defaults:
  format: yaml
  timeout_ms: 30000
guidance:
  use: "when to use this skill, one line"
  not:
    - "other-skill → what it does instead"
methods:
  doSomething:
    description: One sentence on what the method does.
    guidance:
      use: "when the agent has X and needs Y"
      next: "doSomethingElse"
      warn: "the one thing that catches first-time callers"
    http: POST /things
    params:
      thing_id:
        type: uuid
        required: true
        description: The thing to act on.
      filter:
        type: enum
        values: [open, closed, all]
        description: Optional filter.
    response:
      format: yaml
  • Method names must match ^[a-zA-Z0-9_-]+$. OpenAI's API refuses a tool list containing any other name, which breaks every agent that loads your skill.

  • defaults.format: yaml saves a large share of tokens compared with JSON on agent-facing replies.

  • guidance text is added to what the agent sees for each tool: use, next and warn steer it to the right call order without a long SKILL.md.

  • rate_limit: declare one on every method that calls out, or package-wide. The security review checks it.

The full guidance block, nested parameters and the other layers are on The 4-layer skill format.

The response contract

Every skill answers in the same shape, so agents can use any skill without reading its docs. The platform adds these fields from your guidance block; you write no code for them.

  1. _guidance sits beside your data, never around it.

   # right
   { id: "abc", status: "done", _guidance: "…" }
   # wrong: breaks every client that reads the flat shape
   { data: { id: "abc", status: "done" }, _guidance: "…" }
  1. telemetry_signal is one of exactly four values: absent, cold, warm, hot.

  2. Call the free-text input intent, not query, q or prompt. If an older method already uses another name, list it in guidance.intent_aliases.

  3. Skills are called by the manifest skill id. Agents call skill_call (inside OPVS) or tool_call (over MCP) with the id from manifest.skills[].id, or with @vendor/name.

Optional: return diagnostics as a sibling _findings: [...] array, and put structured filters in one additional_filters: {…} object instead of many top-level fields.

If you skip the contract, the skill still loads, but agents lose search ranking, skill_describe answers and the "what next" hints.

What is generated, and when

opvs-skills generate my-skill reads client/schema.yaml and writes the typed tools, MCP definitions, SDK, CLI commands and OpenAPI document. Run it locally to check what agents will see; you do not commit the output.

Next

Profession anatomy, or go straight to The 4-layer skill format.

Did this answer your question?