Skip to content

Latest commit

 

History

History
369 lines (257 loc) · 20.2 KB

File metadata and controls

369 lines (257 loc) · 20.2 KB

Contributing Skills

Thanks for helping improve Scientific Agent Skills. This guide explains how to add or update a skill in this repository while following the open Agent Skills specification.

Participation in this project is governed by our Code of Conduct.

Ways to Contribute

  • Add a new scientific package, database, platform, workflow, or research method skill.
  • Improve an existing skill with clearer instructions, current APIs, better examples, references, or scripts.
  • Fix outdated examples, broken install steps, security issues, or documentation gaps.
  • Add or extend a skill's tests under tests/<skill-name>/ (see Tests).
  • Report bugs or request new skills through GitHub Issues.

Skill Location

All repository skills live under skills/. The repository root is also an Agent Plugins package: keep root plugin.json schema-valid, do not add non-portable top-level fields, and keep its version in sync with pyproject.toml whenever you bump the collection version.

plugin.json
skills/
└── skill-name/
    ├── SKILL.md
    ├── references/
    ├── scripts/
    └── assets/

Only SKILL.md is required. Use optional directories when they make the skill easier to maintain:

  • references/ for longer documentation that agents should read only when needed.
  • scripts/ for executable helpers, validators, or reusable workflow code.
  • assets/ for templates, static resources, or example data.

Those four are the only directories a skill may contain. Anything else — tests, fixtures, scratch data, generated output — belongs outside skills/.

Keep references one level deep from SKILL.md where possible, and keep the main SKILL.md concise. The Agent Skills specification recommends keeping SKILL.md under 500 lines and using progressive disclosure for longer material.

A skill directory holds only what an agent loads, so tests do not belong there. They live in the repository-level suite under tests/<skill-name>/, mirroring the skill directory name, with any fixtures in tests/<skill-name>/fixtures/. See Tests.

Required Skill Format

Every skill must be a directory containing a SKILL.md file with YAML frontmatter followed by Markdown instructions.

Use this minimum template:

---
name: skill-name
description: Clear description of what the skill does and when an agent should use it.
metadata:
  version: "1.0"
  skill-author: Your Name
---

# Skill Title

## When to Use

Use this skill when...

## Workflow

1. ...
2. ...

## Examples

...

Frontmatter Requirements

Follow the Agent Skills specification and this repository's conventions:

  • name is required, must match the parent directory name, and must be 1-64 characters.

  • name may contain only lowercase letters, numbers, and hyphens.

  • name must not start or end with a hyphen and must not contain consecutive hyphens.

  • description is required, non-empty, and must be at most 1024 characters.

  • description should explain both what the skill does and when an agent should use it.

  • metadata.version is required in this repository, even though metadata is optional in the upstream spec.

  • Version values must be quoted numeric strings, such as "1.0" or "1.1".

  • Only the six fields defined by the specification are allowed at the top level: name, description, license, compatibility, allowed-tools, and metadata. The spec defines a closed set and the reference validator rejects any other top-level key, so everything else belongs under metadata.

  • Write metadata as a block mapping, not single-line JSON. The reference validator parses frontmatter with strictyaml, which rejects JSON-style flow mappings. A flow mapping does not merely fail one check — the entire frontmatter fails to parse, so name and description become unreadable and the skill does not register.

    # Wrong -- breaks the reference validator
    metadata: {"version": "1.0", "skill-author": "K-Dense Inc."}
    
    # Right
    metadata:
      version: "1.0"
      skill-author: K-Dense Inc.

Optional frontmatter fields from the specification may be used when relevant:

  • license: the license for the individual skill, if different or worth stating explicitly.
  • compatibility: environment requirements such as Python version, system packages, agent host, or network access. Maximum 500 characters.
  • metadata: additional metadata, as a block mapping of string keys to string values. Quote any value that would otherwise parse as a number, boolean, or date (version: "1.0", last-reviewed: "2026-07-23"). Common keys: version (required), skill-author, and an optional nested openclaw or hermes block (see below).
  • allowed-tools: a space-separated string of tool permissions for hosts that support this experimental field, for example allowed-tools: Read Write Edit Bash. Not a YAML list.

OpenClaw gating (metadata.openclaw)

OpenClaw reads an optional openclaw object nested inside metadata for dependency gating, credential injection, and display. Because it lives under metadata, the Agent Skills spec permits it and other hosts ignore it. It is only needed for skills with external requirements (credentials, daemons, specific binaries) — most skills omit it entirely.

Keep this block a nested mapping — never a JSON string. OpenClaw's resolveOpenClawManifestBlock() requires typeof candidate === "object", so a stringified block silently disables gating and credential injection with no error. This is the one documented exception to the string-values rule for metadata, and it still passes skills-ref validate.

Supported keys:

  • requires: hard eligibility gates — {"bins": [...]} (all must be on PATH), {"anyBins": [...]} (at least one), {"env": [...]} (vars that must be set), {"config": [...]}. A failed gate hides the skill from the agent, so only gate on things the skill genuinely cannot run without.
  • primaryEnv: the main credential variable; OpenClaw injects it from its config (skills.entries.<name>.apiKey).
  • envVars: descriptive (non-gating) declarations — [{"name": "X_API_KEY", "required": true, "description": "..."}]. Declare every env var your scripts reference so ClawHub's security analysis does not flag a metadata mismatch.
  • os: platform filter, e.g. ["darwin", "linux"].
  • emoji, homepage: display only.

Example (an API-key skill that stays available even without the key set, so it gates nothing and only declares the credential):

metadata:
  version: "1.0"
  skill-author: K-Dense Inc.
  openclaw:
    primaryEnv: EXA_API_KEY
    envVars:
      - name: EXA_API_KEY
        required: true
        description: Exa search API key.

Hermes compatibility (required_environment_variables and metadata.hermes)

Hermes is Agent Skills-compatible, so every skill in this repository already loads and runs there with no changes. Two optional fields make credentialed skills first-class on Hermes:

  • metadata.hermes (nested, spec-safe like openclaw): optional classification and gating — tags, category, requires_toolsets, fallback_for_toolsets. A failed requires_toolsets gate hides the skill, so only gate on a tool the skill genuinely cannot run without; prefer leaving it unset so the skill stays available. Keep it a nested mapping, not a JSON string.

Example (an API-key skill, declaring its credential for OpenClaw and classifying itself for Hermes):

metadata:
  version: "1.0"
  skill-author: Exa
  openclaw:
    primaryEnv: EXA_API_KEY
    envVars:
      - name: EXA_API_KEY
        required: true
        description: Exa search API key.
  hermes:
    category: research

required_environment_variables is not used in this repository

Hermes also reads a top-level required_environment_variables array to prompt for credentials. That field cannot coexist with spec conformance: the specification defines a closed set of six top-level fields, so the reference validator rejects it outright — and because strictyaml fails the whole frontmatter block on an unknown-shaped document, the failure is not confined to that one key.

This repository therefore does not use it. Declare credentials in two spec-legal places instead:

  • compatibility — a human- and agent-readable sentence naming the variables the skill needs.
  • metadata.openclaw.envVars — the machine-readable declaration, which ClawHub's security analysis also checks against the variables your scripts actually reference.

Skills still load and run on Hermes; only its automatic credential prompt is unavailable, and the required variables remain discoverable from the two fields above.

Versioning

Every SKILL.md must include a quoted version inside the metadata mapping:

metadata:
  version: "1.0"

For a new skill, start at "1.0".

When updating an existing skill, increment metadata.version in the same pull request:

  • Use a minor bump for normal improvements, for example "1.0" to "1.1".
  • Use a major bump only for a breaking change or substantial redesign, for example "1.9" to "2.0".

Writing a Good Skill

Good skills are specific, practical, and easy for an agent to apply.

  • Write the description in third person with useful trigger terms.
  • Include concrete workflows, commands, and examples instead of broad background explanations.
  • Prefer current official APIs, docs, and installation instructions.
  • Document required Python packages, system dependencies, credentials, or network access.
  • Include scientific best practices, caveats, and validation checks where they matter.
  • Move long API details, tables, and extended examples into references/.
  • Use scripts for fragile or repetitive logic instead of asking the agent to recreate it every time.
  • Avoid secrets, credentials, API keys, private URLs, and unpublished data.

Adding a New Skill

  1. Fork the repository and create a branch:

    git checkout -b add-skill-name
  2. Create a new directory under skills/ whose name matches the skill name:

    skills/skill-name/
    
  3. Add SKILL.md with valid frontmatter, including metadata.version.

  4. Add supporting references/, scripts/, or assets/ only when they are useful.

  5. Test any commands, code examples, and scripts included in the skill.

  6. If the skill ships scripts/, add their tests in the repository-level suite, not in the skill directory:

    tests/skill-name/
    

    See Tests for the layout, the path anchor to use, and how to run them.

  7. Update related documentation if the new skill changes repository-level lists, examples, or setup guidance.

  8. Run validation and security checks before opening a pull request.

Updating an Existing Skill

  1. Read the current SKILL.md and any supporting files.
  2. Check upstream package, API, or platform documentation for current behavior.
  3. Make the smallest useful change that fixes or improves the skill.
  4. Increment metadata.version.
  5. Test changed examples, commands, and scripts.
  6. Run the skill's suite if it has one: uv run --with pytest python -m pytest tests/skill-name -q. Suites check that metadata.version is present and quoted, not what it equals, so a version bump never needs a matching test edit.
  7. Note any behavior changes in the pull request description.

Validation

Validate Agent Skills format with the reference validator, which is already a dev dependency:

uv sync
uv run skills-ref validate ./skills/skill-name

# or check every skill at once, the same way CI does
for d in skills/*/; do uv run skills-ref validate "$d"; done

CI runs this on every pull request that touches skills/, along with the repo-specific checks in .github/workflows/skill-spec-validation.yml (a required metadata.version, allowed-tools as a string, quoted metadata scalars, and a warning above 500 lines).

Security-scan new or substantially changed skills:

uv pip install cisco-ai-skill-scanner
skill-scanner scan ./skills/skill-name --use-behavioral

A clean scan reduces review noise but does not replace manual review.

Tests

Tests never live under skills/. A skill directory ships only what an agent loads, so tests go in the repository-level suite instead — one directory per skill, named exactly after the skill directory:

tests/
└── skill-name/          # matches skills/skill-name/
    ├── test_scripts.py
    └── fixtures/        # optional test data

A test reaches the skill it covers through an explicit anchor rather than a relative walk:

SKILL_ROOT = Path(__file__).resolve().parents[2] / "skills" / "skill-name"

Anything the CLIs under test resolve relative to the working directory should be repo-root relative, since the suite runs from the repository root — tests/skill-name/fixtures/manifest.json, not fixtures/manifest.json.

Run one skill's suite, or the whole tree:

uv run --with pytest python -m pytest tests/skill-name -q

# every skill, in a separate process each, after the repo-wide guard
uv run --with pytest python tests/run_all.py

Each skill's suite must run in its own process. Skills' scripts/ directories own plain top-level module names — 32 skills ship a scripts/_common.py, and names like cluster.py and validate_manifest.py recur — so collecting two skills into one interpreter would resolve those imports to whichever skill was imported first and silently test the wrong files. tests/conftest.py rejects a multi-skill session, and tests/run_all.py forks per skill.

The repo-wide guard, and what you no longer have to write

uv run --with pytest python -m pytest tests/_meta -q

tests/_meta is the check to run first and the one CI blocks on. It needs no scientific packages and finishes in seconds. It spans every skill at once — safe, because it parses scripts with ast and never imports them — and it enforces the rule this whole layout exists for: a skill that ships scripts/ must have a suite at tests/<name>/ and a [skills.<name>] entry in skill-requirements.toml. It also runs the shared structural contract over every skill: frontmatter conformance, the 500-line SKILL.md limit, no tests or compiled bytecode under skills/, every local link resolving, every script parsing, no eval/exec/os.system, no script shadowing a standard-library module, no hardcoded local path, and valid shell scripts.

Because tests/_meta already covers all of that repo-wide, a per-skill suite should not repeat it. Write only what is specific to the skill, and pull the shared pieces from tests/_contract/, which tests/conftest.py registers as the importable module skill_contract:

import skill_contract

# every argparse script answers --help; skips when the skill's packages are
# absent, and runs for real under --isolated
CliHelpTests = skill_contract.cli.help_test_case(SKILL_ROOT)

# for scripts that are importable libraries with a worked example under
# `if __name__ == "__main__":` rather than argparse CLIs
DemoBlockTests = skill_contract.cli.demo_test_case(SKILL_ROOT, ("doe_designs.py",))

skill_contract.office and skill_contract.schematic cover files that several skills ship byte-identical copies of — the OOXML office/ tree under docx/pptx/xlsx, and the AI schematic generator under five skills. Instantiate them against your skill root rather than writing the tests again; tests/_meta separately fails if the copies drift apart, so those files have to be changed together.

Guard heavy imports at module scope so a suite degrades to skips rather than a collection error when a package is missing:

np = pytest.importorskip("numpy", reason="skill-name needs numpy")

One environment per skill

Four suites fail on this repository's default environment because their scientific dependencies are not installed (exa-search, qutip, scikit-survival, simpy), and installing them all into one environment is not possible: the skills' upstream pins contradict each other. opentrons requires numpy<2; esm caps transformers below the release the transformers skill targets; geniml and spikeinterface pin zarr<3 while the zarr-python skill targets 3.x; bioservices caps lxml<6 while matchms requires 6.0.2+; and pytdc, molfeat, deepchem, histolab, vaex, and ete3 each need an interpreter older than 3.13.

--isolated therefore gives each skill its own throwaway uv environment, built from tests/skill-requirements.toml:

python tests/run_all.py --isolated                    # every suite, one env each
python tests/run_all.py --isolated qutip exa-search   # just these

Nothing is installed into the project environment, so uv sync is unaffected. Each [skills.<name>] entry lists the packages that skill documents and, where needed, a python version for that skill alone — uv downloads the interpreter on demand. Packages that cannot be installed at all (a GitHub-only SDK, a conda-forge-only library, a CUDA build) are listed under [unavailable] with the reason, and the runner prints them so the gap appears in the test output.

A new skill that ships scripts/ needs a [skills.<name>] entry — tests/_meta fails without one. Use packages = [] when its bundled tooling is standard-library only — the skill still gets a clean environment with just pytest. uv caches wheels globally, so repeat runs create each environment in milliseconds.

.github/workflows/skill-tests.yml runs tests/_meta plus every packages = [] suite on each pull request, which is fast and needs no wheels beyond pytest. The full --isolated sweep is not run in CI: it builds an environment per skill, and several of them need a CUDA toolchain, a JDK, or a local MATLAB install that a runner does not have. Run it locally before a release, and whenever you change anything under tests/_contract/.

Pull Request Checklist

Before submitting a pull request, confirm:

  • The skill directory name and name frontmatter match exactly.
  • The skill directory contains only SKILL.md, references/, scripts/, and assets/ — no tests/ directory and no test_*.py files. Tests live in tests/<skill-name>/.
  • SKILL.md has valid YAML frontmatter and Markdown body content.
  • uv run skills-ref validate ./skills/<name> passes.
  • Only the six spec-defined top-level fields are present; anything else lives under metadata.
  • metadata is a block mapping, not single-line JSON, and its scalar values are quoted where needed.
  • Any metadata.openclaw or metadata.hermes block is a nested mapping, not a JSON string.
  • If the skill needs credentials, they are named in compatibility and declared in metadata.openclaw.envVars.
  • metadata.version exists and is quoted.
  • Existing skills have a version bump when changed.
  • If the collection version changes, plugin.json version matches pyproject.toml.
  • The description clearly says what the skill does and when to use it.
  • uv run --with pytest python -m pytest tests/_meta -q passes. This is what CI blocks on, and it catches a missing suite, a missing skill-requirements.toml entry, a broken local link, a leaked local path, and a SKILL.md over 500 lines.
  • If the skill ships scripts/: a suite exists at tests/<skill-name>/, a [skills.<skill-name>] entry exists in tests/skill-requirements.toml, and python tests/run_all.py --isolated <skill-name> passes.
  • Examples and scripts have been tested or clearly marked as illustrative.
  • No secrets, credentials, private data, or unsafe instructions are included.
  • Relevant official documentation is linked where useful.
  • Security scanner results are clean or explained in the pull request.

Pull Request Process

  1. Push your branch to your fork.
  2. Open a pull request with a clear title, such as Add scanpy workflow examples or Update astropy skill for current API.
  3. Describe what changed, why it matters, and how you tested it.
  4. Link related issues, package documentation, release notes, or security findings.
  5. Respond to review comments and update the skill as needed.

Thank you for helping make scientific computing more accessible to AI agents and researchers.