← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / scientific-agent-skills / CONTRIBUTING.md

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

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:

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:

```yaml # 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:

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:

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:

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:

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:

Writing a Good Skill

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

Adding a New Skill

  1. Fork the repository and create a branch:

bash git checkout -b add-skill-name

  1. Create a new directory under skills/ whose name matches the skill name:

text skills/skill-name/

  1. Add SKILL.md with valid frontmatter, including metadata.version.

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

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

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

text tests/skill-name/

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

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

  2. 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:

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.