mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
* feat(skills): inject absolute skill dir and expand ${HERMES_SKILL_DIR} templates
When a skill loads, the activation message now exposes the absolute
skill directory and substitutes ${HERMES_SKILL_DIR} /
${HERMES_SESSION_ID} tokens in the SKILL.md body, so skills with
bundled scripts can instruct the agent to run them by absolute path
without an extra skill_view round-trip.
Also adds opt-in inline-shell expansion: !`cmd` snippets in SKILL.md
are pre-executed (with the skill directory as CWD) and their stdout is
inlined into the message before the agent reads it. Off by default —
enable via skills.inline_shell in config.yaml — because any snippet
runs on the host without approval.
Changes:
- agent/skill_commands.py: template substitution, inline-shell
expansion, absolute skill-dir header, supporting-files list now
shows both relative and absolute forms.
- hermes_cli/config.py: new skills.template_vars,
skills.inline_shell, skills.inline_shell_timeout knobs.
- tests/agent/test_skill_commands.py: coverage for header, both
template tokens (present and missing session id), template_vars
disable, inline-shell default-off, enabled, CWD, and timeout.
- website/docs/developer-guide/creating-skills.md: documents the
template tokens, the absolute-path header, and the opt-in inline
shell with its security caveat.
Validation: tests/agent/ 1591 passed (includes 9 new tests).
E2E: loaded a real skill in an isolated HERMES_HOME; confirmed
${HERMES_SKILL_DIR} resolves to the absolute path, ${HERMES_SESSION_ID}
resolves to the passed task_id, !`date` runs when opt-in is set, and
stays literal when it isn't.
* feat(terminal): source ~/.bashrc (and user-listed init files) into session snapshot
bash login shells don't source ~/.bashrc, so tools that install themselves
there — nvm, asdf, pyenv, cargo, custom PATH exports — stay invisible to
the environment snapshot Hermes builds once per session. Under systemd
or any context with a minimal parent env, that surfaces as
'node: command not found' in the terminal tool even though the binary
is reachable from every interactive shell on the machine.
Changes:
- tools/environments/local.py: before the login-shell snapshot bootstrap
runs, prepend guarded 'source <file>' lines for each resolved init
file. Missing files are skipped, each source is wrapped with a
'[ -r ... ] && . ... || true' guard so a broken rc can't abort the
bootstrap.
- hermes_cli/config.py: new terminal.shell_init_files (explicit list,
supports ~ and ${VAR}) and terminal.auto_source_bashrc (default on)
knobs. When shell_init_files is set it takes precedence; when it's
empty and auto_source_bashrc is on, ~/.bashrc gets auto-sourced.
- tests/tools/test_local_shell_init.py: 10 tests covering the resolver
(auto-bashrc, missing file, explicit override, ~/${VAR} expansion,
opt-out) and the prelude builder (quoting, guarded sourcing), plus
a real-LocalEnvironment snapshot test that confirms exports in the
init file land in subsequent commands' environment.
- website/docs/reference/faq.md: documents the fix in Troubleshooting,
including the zsh-user pattern of sourcing ~/.zshrc or nvm.sh
directly via shell_init_files.
Validation: 10/10 new tests pass; tests/tools/test_local_*.py 40/40
pass; tests/agent/ 1591/1591 pass; tests/hermes_cli/test_config.py
50/50 pass. E2E in an isolated HERMES_HOME: confirmed that a fake
~/.bashrc setting a marker var and PATH addition shows up in a real
LocalEnvironment().execute() call, that auto_source_bashrc=false
suppresses it, that an explicit shell_init_files entry wins over the
auto default, and that a missing bashrc is silently skipped.
371 lines
15 KiB
Markdown
371 lines
15 KiB
Markdown
---
|
|
sidebar_position: 3
|
|
title: "Creating Skills"
|
|
description: "How to create skills for Hermes Agent — SKILL.md format, guidelines, and publishing"
|
|
---
|
|
|
|
# Creating Skills
|
|
|
|
Skills are the preferred way to add new capabilities to Hermes Agent. They're easier to create than tools, require no code changes to the agent, and can be shared with the community.
|
|
|
|
## Should it be a Skill or a Tool?
|
|
|
|
Make it a **Skill** when:
|
|
- The capability can be expressed as instructions + shell commands + existing tools
|
|
- It wraps an external CLI or API that the agent can call via `terminal` or `web_extract`
|
|
- It doesn't need custom Python integration or API key management baked into the agent
|
|
- Examples: arXiv search, git workflows, Docker management, PDF processing, email via CLI tools
|
|
|
|
Make it a **Tool** when:
|
|
- It requires end-to-end integration with API keys, auth flows, or multi-component configuration
|
|
- It needs custom processing logic that must execute precisely every time
|
|
- It handles binary data, streaming, or real-time events
|
|
- Examples: browser automation, TTS, vision analysis
|
|
|
|
## Skill Directory Structure
|
|
|
|
Bundled skills live in `skills/` organized by category. Official optional skills use the same structure in `optional-skills/`:
|
|
|
|
```text
|
|
skills/
|
|
├── research/
|
|
│ └── arxiv/
|
|
│ ├── SKILL.md # Required: main instructions
|
|
│ └── scripts/ # Optional: helper scripts
|
|
│ └── search_arxiv.py
|
|
├── productivity/
|
|
│ └── ocr-and-documents/
|
|
│ ├── SKILL.md
|
|
│ ├── scripts/
|
|
│ └── references/
|
|
└── ...
|
|
```
|
|
|
|
## SKILL.md Format
|
|
|
|
```markdown
|
|
---
|
|
name: my-skill
|
|
description: Brief description (shown in skill search results)
|
|
version: 1.0.0
|
|
author: Your Name
|
|
license: MIT
|
|
platforms: [macos, linux] # Optional — restrict to specific OS platforms
|
|
# Valid: macos, linux, windows
|
|
# Omit to load on all platforms (default)
|
|
metadata:
|
|
hermes:
|
|
tags: [Category, Subcategory, Keywords]
|
|
related_skills: [other-skill-name]
|
|
requires_toolsets: [web] # Optional — only show when these toolsets are active
|
|
requires_tools: [web_search] # Optional — only show when these tools are available
|
|
fallback_for_toolsets: [browser] # Optional — hide when these toolsets are active
|
|
fallback_for_tools: [browser_navigate] # Optional — hide when these tools exist
|
|
config: # Optional — config.yaml settings the skill needs
|
|
- key: my.setting
|
|
description: "What this setting controls"
|
|
default: "sensible-default"
|
|
prompt: "Display prompt for setup"
|
|
required_environment_variables: # Optional — env vars the skill needs
|
|
- name: MY_API_KEY
|
|
prompt: "Enter your API key"
|
|
help: "Get one at https://example.com"
|
|
required_for: "API access"
|
|
---
|
|
|
|
# Skill Title
|
|
|
|
Brief intro.
|
|
|
|
## When to Use
|
|
Trigger conditions — when should the agent load this skill?
|
|
|
|
## Quick Reference
|
|
Table of common commands or API calls.
|
|
|
|
## Procedure
|
|
Step-by-step instructions the agent follows.
|
|
|
|
## Pitfalls
|
|
Known failure modes and how to handle them.
|
|
|
|
## Verification
|
|
How the agent confirms it worked.
|
|
```
|
|
|
|
### Platform-Specific Skills
|
|
|
|
Skills can restrict themselves to specific operating systems using the `platforms` field:
|
|
|
|
```yaml
|
|
platforms: [macos] # macOS only (e.g., iMessage, Apple Reminders)
|
|
platforms: [macos, linux] # macOS and Linux
|
|
platforms: [windows] # Windows only
|
|
```
|
|
|
|
When set, the skill is automatically hidden from the system prompt, `skills_list()`, and slash commands on incompatible platforms. If omitted or empty, the skill loads on all platforms (backward compatible).
|
|
|
|
### Conditional Skill Activation
|
|
|
|
Skills can declare dependencies on specific tools or toolsets. This controls whether the skill appears in the system prompt for a given session.
|
|
|
|
```yaml
|
|
metadata:
|
|
hermes:
|
|
requires_toolsets: [web] # Hide if the web toolset is NOT active
|
|
requires_tools: [web_search] # Hide if web_search tool is NOT available
|
|
fallback_for_toolsets: [browser] # Hide if the browser toolset IS active
|
|
fallback_for_tools: [browser_navigate] # Hide if browser_navigate IS available
|
|
```
|
|
|
|
| Field | Behavior |
|
|
|-------|----------|
|
|
| `requires_toolsets` | Skill is **hidden** when ANY listed toolset is **not** available |
|
|
| `requires_tools` | Skill is **hidden** when ANY listed tool is **not** available |
|
|
| `fallback_for_toolsets` | Skill is **hidden** when ANY listed toolset **is** available |
|
|
| `fallback_for_tools` | Skill is **hidden** when ANY listed tool **is** available |
|
|
|
|
**Use case for `fallback_for_*`:** Create a skill that serves as a workaround when a primary tool isn't available. For example, a `duckduckgo-search` skill with `fallback_for_tools: [web_search]` only shows when the web search tool (which requires an API key) is not configured.
|
|
|
|
**Use case for `requires_*`:** Create a skill that only makes sense when certain tools are present. For example, a web scraping workflow skill with `requires_toolsets: [web]` won't clutter the prompt when web tools are disabled.
|
|
|
|
### Environment Variable Requirements
|
|
|
|
Skills can declare environment variables they need. When a skill is loaded via `skill_view`, its required vars are automatically registered for passthrough into sandboxed execution environments (terminal, execute_code).
|
|
|
|
```yaml
|
|
required_environment_variables:
|
|
- name: TENOR_API_KEY
|
|
prompt: "Tenor API key" # Shown when prompting user
|
|
help: "Get your key at https://tenor.com" # Help text or URL
|
|
required_for: "GIF search functionality" # What needs this var
|
|
```
|
|
|
|
Each entry supports:
|
|
- `name` (required) — the environment variable name
|
|
- `prompt` (optional) — prompt text when asking the user for the value
|
|
- `help` (optional) — help text or URL for obtaining the value
|
|
- `required_for` (optional) — describes which feature needs this variable
|
|
|
|
Users can also manually configure passthrough variables in `config.yaml`:
|
|
|
|
```yaml
|
|
terminal:
|
|
env_passthrough:
|
|
- MY_CUSTOM_VAR
|
|
- ANOTHER_VAR
|
|
```
|
|
|
|
See `skills/apple/` for examples of macOS-only skills.
|
|
|
|
## Secure Setup on Load
|
|
|
|
Use `required_environment_variables` when a skill needs an API key or token. Missing values do **not** hide the skill from discovery. Instead, Hermes prompts for them securely when the skill is loaded in the local CLI.
|
|
|
|
```yaml
|
|
required_environment_variables:
|
|
- name: TENOR_API_KEY
|
|
prompt: Tenor API key
|
|
help: Get a key from https://developers.google.com/tenor
|
|
required_for: full functionality
|
|
```
|
|
|
|
The user can skip setup and keep loading the skill. Hermes never exposes the raw secret value to the model. Gateway and messaging sessions show local setup guidance instead of collecting secrets in-band.
|
|
|
|
:::tip Sandbox Passthrough
|
|
When your skill is loaded, any declared `required_environment_variables` that are set are **automatically passed through** to `execute_code` and `terminal` sandboxes — including remote backends like Docker and Modal. Your skill's scripts can access `$TENOR_API_KEY` (or `os.environ["TENOR_API_KEY"]` in Python) without the user needing to configure anything extra. See [Environment Variable Passthrough](/docs/user-guide/security#environment-variable-passthrough) for details.
|
|
:::
|
|
|
|
Legacy `prerequisites.env_vars` remains supported as a backward-compatible alias.
|
|
|
|
### Config Settings (config.yaml)
|
|
|
|
Skills can declare non-secret settings that are stored in `config.yaml` under the `skills.config` namespace. Unlike environment variables (which are secrets stored in `.env`), config settings are for paths, preferences, and other non-sensitive values.
|
|
|
|
```yaml
|
|
metadata:
|
|
hermes:
|
|
config:
|
|
- key: myplugin.path
|
|
description: Path to the plugin data directory
|
|
default: "~/myplugin-data"
|
|
prompt: Plugin data directory path
|
|
- key: myplugin.domain
|
|
description: Domain the plugin operates on
|
|
default: ""
|
|
prompt: Plugin domain (e.g., AI/ML research)
|
|
```
|
|
|
|
Each entry supports:
|
|
- `key` (required) — dotpath for the setting (e.g., `myplugin.path`)
|
|
- `description` (required) — explains what the setting controls
|
|
- `default` (optional) — default value if the user doesn't configure it
|
|
- `prompt` (optional) — prompt text shown during `hermes config migrate`; falls back to `description`
|
|
|
|
**How it works:**
|
|
|
|
1. **Storage:** Values are written to `config.yaml` under `skills.config.<key>`:
|
|
```yaml
|
|
skills:
|
|
config:
|
|
myplugin:
|
|
path: ~/my-data
|
|
```
|
|
|
|
2. **Discovery:** `hermes config migrate` scans all enabled skills, finds unconfigured settings, and prompts the user. Settings also appear in `hermes config show` under "Skill Settings."
|
|
|
|
3. **Runtime injection:** When a skill loads, its config values are resolved and appended to the skill message:
|
|
```
|
|
[Skill config (from ~/.hermes/config.yaml):
|
|
myplugin.path = /home/user/my-data
|
|
]
|
|
```
|
|
The agent sees the configured values without needing to read `config.yaml` itself.
|
|
|
|
4. **Manual setup:** Users can also set values directly:
|
|
```bash
|
|
hermes config set skills.config.myplugin.path ~/my-data
|
|
```
|
|
|
|
:::tip When to use which
|
|
Use `required_environment_variables` for API keys, tokens, and other **secrets** (stored in `~/.hermes/.env`, never shown to the model). Use `config` for **paths, preferences, and non-sensitive settings** (stored in `config.yaml`, visible in config show).
|
|
:::
|
|
|
|
### Credential File Requirements (OAuth tokens, etc.)
|
|
|
|
Skills that use OAuth or file-based credentials can declare files that need to be mounted into remote sandboxes. This is for credentials stored as **files** (not env vars) — typically OAuth token files produced by a setup script.
|
|
|
|
```yaml
|
|
required_credential_files:
|
|
- path: google_token.json
|
|
description: Google OAuth2 token (created by setup script)
|
|
- path: google_client_secret.json
|
|
description: Google OAuth2 client credentials
|
|
```
|
|
|
|
Each entry supports:
|
|
- `path` (required) — file path relative to `~/.hermes/`
|
|
- `description` (optional) — explains what the file is and how it's created
|
|
|
|
When loaded, Hermes checks if these files exist. Missing files trigger `setup_needed`. Existing files are automatically:
|
|
- **Mounted into Docker** containers as read-only bind mounts
|
|
- **Synced into Modal** sandboxes (at creation + before each command, so mid-session OAuth works)
|
|
- Available on **local** backend without any special handling
|
|
|
|
:::tip When to use which
|
|
Use `required_environment_variables` for simple API keys and tokens (strings stored in `~/.hermes/.env`). Use `required_credential_files` for OAuth token files, client secrets, service account JSON, certificates, or any credential that's a file on disk.
|
|
:::
|
|
|
|
See the `skills/productivity/google-workspace/SKILL.md` for a complete example using both.
|
|
|
|
## Skill Guidelines
|
|
|
|
### No External Dependencies
|
|
|
|
Prefer stdlib Python, curl, and existing Hermes tools (`web_extract`, `terminal`, `read_file`). If a dependency is needed, document installation steps in the skill.
|
|
|
|
### Progressive Disclosure
|
|
|
|
Put the most common workflow first. Edge cases and advanced usage go at the bottom. This keeps token usage low for common tasks.
|
|
|
|
### Include Helper Scripts
|
|
|
|
For XML/JSON parsing or complex logic, include helper scripts in `scripts/` — don't expect the LLM to write parsers inline every time.
|
|
|
|
#### Referencing bundled scripts from SKILL.md
|
|
|
|
When a skill is loaded, the activation message exposes the absolute skill directory as `[Skill directory: /abs/path]` and also substitutes two template tokens anywhere in the SKILL.md body:
|
|
|
|
| Token | Replaced with |
|
|
|---|---|
|
|
| `${HERMES_SKILL_DIR}` | Absolute path to the skill's directory |
|
|
| `${HERMES_SESSION_ID}` | The active session id (left in place if there is no session) |
|
|
|
|
So a SKILL.md can tell the agent to run a bundled script directly with:
|
|
|
|
```markdown
|
|
To analyse the input, run:
|
|
|
|
node ${HERMES_SKILL_DIR}/scripts/analyse.js <input>
|
|
```
|
|
|
|
The agent sees the substituted absolute path and invokes the `terminal` tool with a ready-to-run command — no path math, no extra `skill_view` round-trip. Disable substitution globally with `skills.template_vars: false` in `config.yaml`.
|
|
|
|
#### Inline shell snippets (opt-in)
|
|
|
|
Skills can also embed inline shell snippets written as `` !`cmd` `` in the SKILL.md body. When enabled, each snippet's stdout is inlined into the message before the agent reads it, so skills can inject dynamic context:
|
|
|
|
```markdown
|
|
Current date: !`date -u +%Y-%m-%d`
|
|
Git branch: !`git -C ${HERMES_SKILL_DIR} rev-parse --abbrev-ref HEAD`
|
|
```
|
|
|
|
This is **off by default** — any snippet in a SKILL.md runs on the host without approval, so only enable it for skill sources you trust:
|
|
|
|
```yaml
|
|
# config.yaml
|
|
skills:
|
|
inline_shell: true
|
|
inline_shell_timeout: 10 # seconds per snippet
|
|
```
|
|
|
|
Snippets run with the skill directory as their working directory, and output is capped at 4000 characters. Failures (timeouts, non-zero exits) show up as a short `[inline-shell error: ...]` marker instead of breaking the whole skill.
|
|
|
|
### Test It
|
|
|
|
Run the skill and verify the agent follows the instructions correctly:
|
|
|
|
```bash
|
|
hermes chat --toolsets skills -q "Use the X skill to do Y"
|
|
```
|
|
|
|
## Where Should the Skill Live?
|
|
|
|
Bundled skills (in `skills/`) ship with every Hermes install. They should be **broadly useful to most users**:
|
|
|
|
- Document handling, web research, common dev workflows, system administration
|
|
- Used regularly by a wide range of people
|
|
|
|
If your skill is official and useful but not universally needed (e.g., a paid service integration, a heavyweight dependency), put it in **`optional-skills/`** — it ships with the repo, is discoverable via `hermes skills browse` (labeled "official"), and installs with builtin trust.
|
|
|
|
If your skill is specialized, community-contributed, or niche, it's better suited for a **Skills Hub** — upload it to a registry and share it via `hermes skills install`.
|
|
|
|
## Publishing Skills
|
|
|
|
### To the Skills Hub
|
|
|
|
```bash
|
|
hermes skills publish skills/my-skill --to github --repo owner/repo
|
|
```
|
|
|
|
### To a Custom Repository
|
|
|
|
Add your repo as a tap:
|
|
|
|
```bash
|
|
hermes skills tap add owner/repo
|
|
```
|
|
|
|
Users can then search and install from your repository.
|
|
|
|
## Security Scanning
|
|
|
|
All hub-installed skills go through a security scanner that checks for:
|
|
|
|
- Data exfiltration patterns
|
|
- Prompt injection attempts
|
|
- Destructive commands
|
|
- Shell injection
|
|
|
|
Trust levels:
|
|
- `builtin` — ships with Hermes (always trusted)
|
|
- `official` — from `optional-skills/` in the repo (builtin trust, no third-party warning)
|
|
- `trusted` — from openai/skills, anthropics/skills
|
|
- `community` — non-dangerous findings can be overridden with `--force`; `dangerous` verdicts remain blocked
|
|
|
|
Hermes can now consume third-party skills from multiple external discovery models:
|
|
- direct GitHub identifiers (for example `openai/skills/k8s`)
|
|
- `skills.sh` identifiers (for example `skills-sh/vercel-labs/json-render/json-render-react`)
|
|
- well-known endpoints served from `/.well-known/skills/index.json`
|
|
|
|
If you want your skills to be discoverable without a GitHub-specific installer, consider serving them from a well-known endpoint in addition to publishing them in a repo or marketplace.
|