Authoring In-Repo Hermes Agent Skills: SKILL.md Structure and Standards
Author in-repo SKILL.md files: frontmatter and structure.
Written by Neura Market from the official Hermes Agent documentation for Hermes Agent Skill Authoring. Commands, paths, and version numbers are reproduced from the source unchanged.
Read the official documentationThis guide walks through the exact process for authoring a SKILL.md file that lives inside the hermes-agent repository, as opposed to a personal skill in your home directory. You would reach for this when you are committing a reusable workflow that should ship with the package, or when you are editing an existing skill under skills/ or optional-skills/. It covers the hardline authoring standards, required frontmatter, platform gating, body structure, and the verification checklist that reviewers enforce.
What it does
A SKILL.md file is the single source of truth for a skill: it tells the agent what the skill does, when to use it, what it needs, and how to run it. In-repo skills are committed to the hermes-agent repository and ship with the package, so they must meet stricter standards than user-local skills. This skill is the operational walkthrough for meeting those standards. It explains the difference between bundled and optional skills, the exact frontmatter fields the validator and reviewers expect, how to set platforms: honestly, how to structure the body, and how to run tests and regenerate docs without polluting the diff.
Before you start
You need write access to the hermes-agent repository and a working branch. The skill lives at skills/<category>/<name>/SKILL.md or optional-skills/<category>/<name>/SKILL.md. You will use write_file to create or rewrite the file, and git add to stage it. skill_manage(action='create') does NOT target the in-repo tree; it writes to ~/.hermes/skills/, so do not use it here. The repo's AGENTS.md section "Skill authoring standards (HARDLINE)" is the source of truth; this skill is the operational walkthrough. Reviewers reject PRs that violate those standards, so meeting them up front is cheaper than a salvage pass later.
Decide the Tier First: Bundled vs Optional
Before writing anything, decide whether the skill belongs in skills/ (bundled) or optional-skills/ (optional).
- Bundled (
skills/<category>/<name>/), daily-driver behavior, broadly useful across many user types, low footprint. The hard bar: you can say "a user will load this in 5+ sessions per month" with a straight face. - Optional (
optional-skills/<category>/<name>/), niche, vertical-specific (blockchain, gaming, finance, one app), recurring-job/task skills, or anything heavy. Installed viahermes skills install official/<category>/<name>.
When in doubt, optional. Promoting later is easy; demoting is churn. "Would be useful to anyone who ever needs this" is an optional-tier argument, not a bundled one.
Pick the category by what the tool IS, not what it feels like (an AI-agent CLI goes in autonomous-ai-agents/ even if it "feels productivity"). Confirm existing categories with search_files(pattern='*', target='files', path='skills') and don't invent new top-level categories casually.
No router / index / hub skills. A skill whose core content is a routing table pointing at sibling skills adds an indirection hop and duplicates the siblings' own When to Use triggers. If the skill would be empty without "load skill X instead" pointers, don't write it, the catalog and each sibling's triggers already do that job.
Required Frontmatter
The validator source of truth is tools/skill_manager_tool.py::_validate_frontmatter. The validator hard requirements are:
- Starts with
---as the first bytes (no leading blank line). - Closes with
\n---\nbefore the body. - Parses as a YAML mapping.
namefield present.descriptionfield present (validator ceiling 1024 chars, but see the repo hardline below, which is much stricter).- Non-empty body after the closing
---.
The repo-standard shape expects all fields, even where the validator doesn't enforce them:
---
name: my-skill-name # lowercase, hyphens, ≤64 chars (MAX_NAME_LENGTH)
description: Concise capability statement, under sixty chars.
version: 0.1.0 # semver; new skills start at 0.1.0
author: Real Name (github-handle), Hermes Agent
license: MIT
platforms: [linux, macos, windows] # audit, don't guess — see Platform Gating
metadata:
hermes:
tags: [Short, Descriptive, Tags]
related_skills: [other-in-repo-skill]
---
description rules (HARDLINE, the validator's 1024 is NOT the standard)
- ≤ 60 characters. One sentence. Ends with a period.
- State the capability, not the implementation, and don't repeat the skill name.
- No marketing words ("powerful", "comprehensive", "seamless", "advanced").
- The system prompt skill index truncates at 57 chars + "...", the trigger/capability must be self-contained in that window.
- If the description contains a
:, wrap it in double quotes or YAML parses it as a mapping and the docs generator crashes. Quotes don't count toward the 60.
Good: Track named companies for material news with cited digests. Bad: Use when a user asks to monitor named competitors or companies for product launches, pricing changes, funding, ... (240 chars, rejected in review)
author rules
- Credit the human first, then "Hermes Agent" as secondary collaborator:
Ben Barclay (benbarclay), Hermes Agent. - Never
author: Hermes Agentalone for contributed skills, credit the human, not the tool, even (especially) when an agent drafted the text. - Maintainer-authored skills:
Teknium (teknium1), Hermes Agent.
related_skills rules
- Every entry must resolve to an existing in-repo skill in the same tree state as your PR. Do not reference skills that were only planned, live in another PR, or exist only in
~/.hermes/skills/. - Verify each entry:
search_files(pattern='', target='files', path='skills')(andoptional-skills/).
Platform Gating: audit, don't trust
platforms: gates loading by host OS. Set it from what the skill's prose and scripts actually invoke:
| Skill uses only… | platforms: |
|---|---|
| Hermes tools + stdlib Python + cross-platform CLIs | [linux, macos, windows] |
bash pipelines, grep/awk/sed chains, heredocs | [linux, macos] |
osascript, defaults, pmset | [macos] |
apt/systemctl//proc | [linux] |
POSIX-only signals to search for in scripts/: fcntl, termios, pty, os.fork, os.killpg, signal.SIGKILL, os.kill(pid, 0) liveness checks, hardcoded /tmp /proc /etc. Default posture: fix cross-platform first (tempfile.gettempdir(), pathlib.Path, psutil.pid_exists); gate narrower only when the dependency is genuinely platform-bound, and say why in ## Pitfalls.
Size Limits
- Full SKILL.md: ≤ 100,000 chars enforced (
MAX_SKILL_CONTENT_CHARS), but target ~100 lines for a simple skill, ~200 for a complex one. Peer skills sit at 8-14k chars. - Bulky or branch-specific material goes in
references/*.md,templates/, orscripts/, pointed to from SKILL.md, not inlined. - Don't expect the model to inline-write parsers or non-trivial logic every call, ship a helper script in
scripts/and reference it by path.
Body Structure (modern section order)
# <Skill> Skill
2-3 sentence intro: what it does, what it doesn't do, dependency stance.
## When to Use — bulleted triggers (+ "Don't use for:" counter-triggers)
## Prerequisites — exact env vars, installs, API key sourcing
## How to Run — canonical invocation through the `terminal` tool
## Quick Reference — flat command list, no narration
## Procedure — numbered steps, each with a checkable completion criterion
## Pitfalls — known limits, things that look broken but aren't
## Verification — how to prove the skill worked
Not every section applies to every skill (a pure-procedure task skill may have no Quick Reference), but When to Use + actionable body + Pitfalls + Verification are the minimum. Cut marketing intros, "Setup Check" no-ops, and re-explanations of env vars already in Prerequisites.
Reference Hermes tools, not raw shell
When the skill needs a capability, name the proper Hermes tool in backticks: terminal, read_file, write_file, patch, search_files, web_search, web_extract, browser_navigate, vision_analyze, delegate_task, cronjob. Do NOT name shell utilities the agent already has wrapped (grep → search_files, cat → read_file, sed/awk → patch, find/ls → search_files target='files'). A CLI-wrapper skill should frame invocations as terminal(command=" ...", timeout=...), bare shell prose ("run foo --version") is a review-blocking non-conformance. If the skill depends on an MCP server, name it and document setup in Prerequisites.
Never use machine-local paths
Write repo-relative paths (skills/..., tools/skill_manager_tool.py). A /home/<user>/... path baked into a committed skill breaks for every other user and is an instant review flag.
Writing Quality Principles
A skill exists to make the agent's process more predictable, the agent reliably follows the same useful discipline.
- Optimize for process predictability. If a line does not change behavior, cut it.
- Choose the right context load. The description is paid for every turn; details go in the body or linked references.
- End steps with completion criteria. Checkable and, when it matters, exhaustive: "every modified file accounted for" beats "summarize changes."
- Co-locate rules with the concept they govern.
- Use strong leading words ("tight loop," "root cause," "regression test") over long repeated explanations.
- Prune duplication and no-ops. "Be careful" and "use best practices" don't change model behavior, replace with a checkable criterion or delete.
Tests and Docs (required for repo skills)
- Tests live at
tests/skills/test_<skill>.py, stdlib + pytest +unittest.mockonly, no live network. Run viascripts/run_tests.sh tests/skills/test_<skill>.py -q. (The generictests/tools/test_skill_manager_tool.pypassing proves nothing about YOUR skill.) - Docs regen: run
python website/scripts/generate-skill-docs.py, then apply scope discipline, the generator rewrites EVERY auto-gen page.git checkout --everything that isn't yours; the final diff must show only your SKILL.md, your one per-skill docs page, a one-line catalog row, and a one-linewebsite/sidebars.tsinsertion (verify withsearch_files(pattern='', path='website/sidebars.ts'), exactly one hit, or the page is an orphan). .env.example(only if the skill needs new env vars): one clearly delimited commented block; touch nothing else in the file.
Workflow
- Survey peers in the target category with
search_files(target='files')and read 2-3 peer SKILL.md files to match tone and structure. Prefer extending an existing skill over creating a narrow sibling. - Decide tier and category (see above). When in doubt, optional, and ask before pushing rather than defaulting.
- Draft with
write_filetoskills/<category>/<name>/SKILL.md(oroptional-skills/...). - Validate locally:
import yaml, re, pathlib
content = pathlib.Path("skills/<category>/<name>/SKILL.md").read_text()
assert content.startswith("---")
m = re.search(r'\n---\s*\n', content[3:])
fm = yaml.safe_load(content[3:m.start()+3])
assert "name" in fm and "description" in fm
assert len(fm["description"]) <= 60, f"description {len(fm['description'])} chars — hardline is 60"
assert fm["description"].endswith(".")
assert "platforms" in fm
assert len(content) <= 100_000
Also verify every related_skills entry exists in-repo.
5. Add tests + regen docs (previous section).
6. Git add + commit on the active branch; open a PR.
7. Note: the CURRENT session's skill loader is cached, skill_view / skills_list will not see the new skill until a new session. This is expected, not a bug.
Editing Existing In-Repo Skills
- Small fix:
skill_manage(action='patch', ...)works on in-repo skills, as doespatch. - Major rewrite:
write_filethe whole SKILL.md. - Supporting files:
write_filetoreferences/,templates/, orscripts/under the skill dir. - Always commit, in-repo skills are source, not runtime state. Re-run the docs generator when frontmatter changed.
Common Pitfalls
- Using
skill_manage(action='create')for an in-repo skill. It writes to~/.hermes/skills/, not the repo tree. Usewrite_file. - Trusting the validator's limits as the standard. The validator allows 1024-char descriptions; review rejects anything over 60. The validator doesn't check
platforms:, author format, tests, or docs, review does. author: Hermes Agenton a contributed skill. Credit the human first.- Leading whitespace before
---. Validation fails on any leading blank line or BOM. - Description too generic or trigger buried past char 57.
related_skillspointing at skills that don't exist in-repo (user-local, planned, or in a sibling PR).- Duplicating a peer. Survey the category first; extend rather than sibling.
- Skipping the docs generator or pushing its unrelated drift. Both directions are wrong: no regen = orphan skill with no docs page; blind regen = a ballooned diff full of other skills' drift.
- Expecting the current session to see the new skill. The loader is initialized at session start.
- Letting skills accumulate sediment. When adding a rule, remove the old wording it replaces.
Verification Checklist
- Tier decided deliberately (bundled bar: 5+ sessions/month; else
optional-skills/) - File at
skills/<category>/<name>/SKILL.mdoroptional-skills/<category>/<name>/SKILL.md - Frontmatter starts at byte 0 with
---, closes with\n---\n name,description,version,author,license,platforms,metadata.hermes.{tags, related_skills}all present- Description ≤ 60 chars, one sentence, ends with a period, no marketing words
authorcredits the human contributor firstplatforms:audited against actual prose/scripts, not copied from a sibling- Every
related_skillsentry resolves in-repo - Body follows the modern section order; commands framed through Hermes tools
- No machine-local paths anywhere in the file
- Each ordered step has a checkable completion criterion
- Tests at
tests/skills/test_<skill>.pypass underscripts/run_tests.sh - Docs regenerated with scope discipline; sidebar has exactly one entry for the slug
git add+ commit on the intended branch; PR opened
When not to use it
Do not use this skill for personal skills that live in ~/.hermes/skills/. Those are created with skill_manage(action='create') and do not need to meet the repo's hardline standards. Also avoid it for skills that would be a router or index pointing at sibling skills; the catalog and each sibling's own When to Use triggers already handle that. If the skill is niche or heavy, prefer optional-skills/ over bundled.
Limits and gotchas
The most common failure is using skill_manage(action='create') for an in-repo skill; it writes to the user-local tree, not the repo. Another is trusting the validator's 1024-char description ceiling when review enforces 60. The validator does not check platforms:, author format, tests, or docs, so you must audit those yourself. Leading whitespace before the opening --- fails validation. The current session's skill loader is cached, so skill_view and skills_list will not show the new skill until a new session; that is expected. When regenerating docs, the generator rewrites every auto-gen page, so scope discipline is essential: revert unrelated changes and keep the diff to your SKILL.md, one docs page, one catalog row, and one sidebar entry.
What pairs with this
This skill pairs with plan for structuring multi-step authoring work, and with requesting-code-review for getting feedback on your PR before it goes out. Both are related in-repo skills you can reference in your frontmatter.