Authoring In-Repo Hermes Agent Skills: SKILL.md Reference
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 skill walks you through writing a SKILL.md that ships inside the hermes-agent repository itself, rather than one that lives only on your machine. It is the operational companion to the repo's hardline authoring standards, and it exists because a skill that fails those standards gets rejected at review. Reach for it when a reusable workflow should be committed and distributed with the package, or when you are editing a skill already under skills/ or optional-skills/.
What it does
A SKILL.md can live in two places. The first is user-local, under ~/.hermes/skills///SKILL.md, which is personal and not shared, and which you create with skill_manage(action='create'). The second is in-repo: skills///SKILL.md or optional-skills///SKILL.md inside the hermes-agent repository, committed and shipped with the package. That second case is what this skill covers. You write the file with write_file and stage it with git add; skill_manage(action='create') does not target the repo tree.
In-repo skills have to satisfy the repository's hardline authoring standards. The authoritative text is the "Skill authoring standards (HARDLINE)" section of AGENTS.md; this skill is the walkthrough that gets you there. Reviewers reject pull requests that break those rules, so building to the standard from the start costs less than repairing a submission afterward.
Before you start
You need a checkout of the hermes-agent repository and a branch to commit on. The skill is bundled with Hermes Agent (version 2.0.0, MIT licensed, authored by Hermes Agent) and runs on linux, macos, and windows, so the platform itself is not a constraint.
The local validation step in the workflow imports ruamel.yaml, so that package has to be available in the Python environment you run the check from. The test step relies on pytest and unittest.mock, both standard tooling, and the tests are expected to run without live network access.
Before drafting, survey the category you intend to write into. Read two or three peer SKILL.md files so your tone and structure match what is already there, and prefer extending an existing skill over adding a narrow sibling.
Decide the tier first: bundled vs optional
The tier decision drives the path, the review bar, and how users get the skill.
Bundled skills live under skills//. They are daily-driver behavior: broadly useful across many user types and low in footprint. The bar is strict. You should be able to say, honestly, that a user will load the skill in five or more sessions per month.
Optional skills live under optional-skills//. This tier covers niche or vertical-specific work (blockchain, gaming, finance, a single application), recurring job and task skills, and anything heavy. Users install them with hermes skills install official//.
When you are unsure, choose optional. Promoting a skill to bundled later is easy; demoting one creates churn. The argument "this would be useful to anyone who ever needs it" supports the optional tier, not the bundled one.
Pick the category by what the tool is, not by what it feels like. An AI-agent CLI belongs in autonomous-ai-agents/ even if it feels like a productivity tool. Confirm which categories already exist with search_files(pattern='*', target='files', path='skills'), and do not invent new top-level categories casually.
One structural rule: no router, index, or hub skills. A skill whose main 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, do not write it. The catalog and each sibling's triggers already do that job.
Required frontmatter
The validator's source of truth is tools/skill_manager_tool.py::_validate_frontmatter. Its hard requirements are:
- The file starts with
---as its first bytes, with no leading blank line. - It closes with
\n---\nbefore the body. - The frontmatter parses as a YAML mapping.
- A
namefield is present. - A
descriptionfield is present. The validator ceiling is 1024 characters, but the repo hardline below is much stricter. - The body after the closing
---is non-empty.
The repo-standard shape expects every field, even where the validator does not enforce it:
---
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
This is a hardline area, and the validator's 1024-character allowance is not the standard you are held to.
Keep the description to 60 characters or fewer: one sentence, ending with a period. State the capability rather than the implementation, and do not repeat the skill name. Marketing words such as "powerful", "comprehensive", "seamless", or "advanced" are out.
The system prompt's skill index truncates at 57 characters plus "...", so the trigger or capability has to stand on its own inside that window. If the description contains a colon, wrap it in double quotes or YAML will parse it as a mapping and the docs generator will crash. The quotes do not count toward the 60.
A good example from the source: Track named companies for material news with cited digests. A bad one runs to 240 characters and was rejected in review: Use when a user asks to monitor named competitors or companies for product launches, pricing changes, funding, ...
Author rules
Credit the human first, with "Hermes Agent" as secondary collaborator: Ben Barclay (benbarclay), Hermes Agent. Never write author: Hermes Agent alone on a contributed skill. Credit the human, not the tool, even when an agent drafted the text, and especially then. Maintainer-authored skills use Teknium (teknium1), Hermes Agent.
Related skills rules
Every entry has to resolve to an existing in-repo skill in the same tree state as your pull request. Do not reference skills that were only planned, that live in another PR, or that exist only under ~/.hermes/skills/. Verify each entry with search_files(pattern='', target='files', path='skills'), and check optional-skills/ the same way.
Platform gating: audit, don't trust
The platforms: field gates loading by host operating system. Set it from what the skill's prose and scripts actually invoke, not from what a sibling skill uses.
| 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] |
Search scripts/ for POSIX-only signals: fcntl, termios, pty, os.fork, os.killpg, signal.SIGKILL, os.kill(pid, 0) liveness checks, and hardcoded /tmp, /proc, or /etc paths. The default posture is to fix cross-platform compatibility first, using tempfile.gettempdir(), pathlib.Path, and psutil.pid_exists. Gate narrower only when the dependency is genuinely bound to one platform, and explain why in ## Pitfalls.
Size limits
The full SKILL.md is capped at 100,000 characters, enforced as MAX_SKILL_CONTENT_CHARS. Aim well below that: roughly 100 lines for a simple skill and about 200 for a complex one. Peer skills sit at 8 to 14 thousand characters.
Bulky or branch-specific material belongs in references/*.md, templates/, or scripts/, pointed to from SKILL.md rather than inlined. Do not expect the model to write parsers or other non-trivial logic inline on every call. Ship a helper script in scripts/ and reference it by path.
Body structure
The modern section order looks like this:
# <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. The minimum is When to Use, an actionable body, Pitfalls, and Verification. Cut marketing intros, "Setup Check" no-ops, and re-explanations of environment variables already covered 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_exec, vision_analyze, delegate_task, cronjob. Do not name shell utilities the agent already has wrapped. grep becomes search_files, cat becomes read_file, sed and awk become patch, and find or ls become search_files target='files'.
A CLI-wrapper skill should frame its invocations as terminal(command="{tool} ...", timeout=...). Bare shell prose such as "run foo --version" is a review-blocking non-conformance. If the skill depends on an MCP server, name it and document the setup in Prerequisites.
Never use machine-local paths
Write repo-relative paths such as skills/... or tools/skill_manager_tool.py. A /home//... 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, so that 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 on every turn; details go in the body or in linked references.
- End steps with completion criteria. Make them checkable and, where it matters, exhaustive: "every modified file accounted for" beats "summarize changes".
- Co-locate rules with the concept they govern.
- Use strong leading words such as "tight loop", "root cause", or "regression test" instead of long repeated explanations.
- Prune duplication and no-ops. "Be careful" and "use best practices" do not change model behavior, so replace them with a checkable criterion or delete them.
Tests and docs
Repo skills require both.
Tests live at tests/skills/test__skill.py and use only the standard library, pytest, and unittest.mock, with no live network. Run them with scripts/run_tests.sh tests/skills/test__skill.py -q. The generic tests/tools/test_skill_manager_tool.py passing proves nothing about your skill.
For docs, run python website/scripts/generate-skill-docs.py, then apply scope discipline. The generator rewrites every auto-generated page. Use git checkout -- on everything that is not yours, so the final diff shows only your SKILL.md, your one per-skill docs page, a one-line catalog row, and a one-line insertion in website/sidebars.ts. Verify the sidebar with search_files(pattern='', path='website/sidebars.ts'): exactly one hit, or the page is an orphan.
If the skill needs new environment variables, add them to .env.example as one clearly delimited commented block and touch nothing else in that file.
Workflow
- Survey peers in the target category with
search_files(target='files')and read two or three peer SKILL.md files to match tone and structure. Prefer extending an existing skill over creating a narrow sibling. - Decide tier and category as described above. When in doubt, choose optional, and ask before pushing rather than defaulting.
- Draft with
write_filetoskills///SKILL.md, or to the corresponding path underoptional-skills/. - Validate locally with the following script:
import re, pathlib
from ruamel.yaml import YAML
content = pathlib.Path("skills/<category>/<name>/SKILL.md").read_text()
assert content.startswith("---")
m = re.search(r'\n---\s*\n', content[3:])
fm = YAML(typ="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 that every related_skills entry exists in the repo.
5. Add tests and regenerate docs, as covered in the previous section.
6. git add and commit on the active branch, then open a pull request.
7. Expect one quirk: the current session's skill loader is cached. skill_view and skills_list will not see the new skill until a new session starts. That is expected behavior, not a bug.
Editing existing in-repo skills
For a small fix, skill_manage(action='patch', ...) works on in-repo skills, and so does patch. For a major rewrite, write_file the whole SKILL.md. Supporting files go through write_file into references/, templates/, or scripts/ under the skill directory.
Always commit. In-repo skills are source, not runtime state. Re-run the docs generator whenever the frontmatter changed.
When not to use it
If you are writing a personal skill under ~/.hermes/skills/, this is the wrong tool. Use skill_manage directly for that case. The in-repo path, the hardline frontmatter rules, the tests, and the docs regeneration all exist because the skill ships to other people; none of it applies to a skill that stays on your machine.
Limits and gotchas
- Using
skill_manage(action='create')for an in-repo skill. It writes to~/.hermes/skills/, not the repo tree. Usewrite_file. - Treating the validator's limits as the standard. The validator allows 1024-character descriptions; review rejects anything over 60. The validator also does not check
platforms:, author format, tests, or docs. Review does. - Writing
author: Hermes Agenton a contributed skill. Credit the human first. - Leading whitespace before
---. Validation fails on any leading blank line or BOM. - A description that is too generic, or a trigger buried past character 57.
related_skillspointing at skills that do not exist in the repo, whether user-local, planned, or in a sibling PR.- Duplicating a peer. Survey the category first and extend rather than add a sibling.
- Skipping the docs generator, or pushing its unrelated drift. Both directions are wrong: no regeneration leaves an orphan skill with no docs page, and a blind regeneration produces 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 you add a rule, remove the old wording it replaces.
Verification checklist
- Tier decided deliberately, with the bundled bar at five or more sessions per month; otherwise
optional-skills/. - File at
skills///SKILL.mdoroptional-skills///SKILL.md. - Frontmatter starts at byte 0 with
---and closes with\n---\n. name,description,version,author,license,platforms, andmetadata.hermes.{tags, related_skills}are all present.- Description is 60 characters or fewer, one sentence, ends with a period, and contains no marketing words.
authorcredits the human contributor first.platforms:was audited against the actual prose and scripts, not copied from a sibling.- Every
related_skillsentry resolves in the repo. - The body follows the modern section order, and commands are 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, and the sidebar has exactly one entry for the slug.
git addand commit on the intended branch, with a pull request opened.
What pairs with this
Once the skill is drafted, the natural next step is review. The related skill requesting-code-review covers that side of the workflow, and it is the right companion when you want a second pass over the SKILL.md, its tests, and the diff before the pull request goes out.