Some checks failed
Typecheck / typecheck (apps/shared) (pull_request) Has been cancelled
Contributor Attribution Check / check-attribution (pull_request) Has been cancelled
History Check / check-common-ancestor (pull_request) Has been cancelled
Nix / nix (macos-latest) (pull_request) Has been cancelled
Nix / nix (ubuntu-latest) (pull_request) Has been cancelled
Typecheck / typecheck (ui-tui) (pull_request) Has been cancelled
Supply Chain Audit / changes (pull_request) Has been cancelled
Typecheck / typecheck (apps/bootstrap-installer) (pull_request) Has been cancelled
Typecheck / typecheck (apps/desktop) (pull_request) Has been cancelled
Typecheck / typecheck (web) (pull_request) Has been cancelled
Supply Chain Audit / Scan PR for critical supply chain risks (pull_request) Has been cancelled
Supply Chain Audit / Check PyPI dependency upper bounds (pull_request) Has been cancelled
Rewrite 'do not X' / 'never Y' / 'avoid Z' / 'cannot W' patterns across documentation files into positive 'do ABC to achieve XYZ' instructions. Files changed: - AGENTS.md (180 lines): 30+ patterns converted including caching, profiles, known pitfalls, testing, change-detector tests - CONTRIBUTING.md (50 lines): 14+ patterns including memory providers, cross-platform rules, skill authoring, security - README-FreeBSD.md: operator-user instructions - apps/desktop/DESIGN.md (49 lines): 12 design constraint patterns - docs/observability/README.md: 4 observer contract patterns Hard safety invariants preserved: - Secrets never in logs → 'Keep secrets out of logs. Redact from log output' - Tests never write to ~/.hermes → 'Use _isolate_hermes_home fixture' - Prompt cache never broken → 'Past context stays immutable mid-conversation'
922 lines
44 KiB
Markdown
922 lines
44 KiB
Markdown
# Contributing to Hermes Agent
|
|
|
|
Thank you for contributing to Hermes Agent! This guide covers everything you need: setting up your dev environment, understanding the architecture, deciding what to build, and getting your PR merged.
|
|
|
|
---
|
|
|
|
## Contribution Priorities
|
|
|
|
We value contributions in this order:
|
|
|
|
1. **Bug fixes** — crashes, incorrect behavior, data loss. Always top priority.
|
|
2. **Cross-platform compatibility** — macOS, different Linux distros, and WSL2 on Windows. We want Hermes to work everywhere.
|
|
3. **Security hardening** — shell injection, prompt injection, path traversal, privilege escalation. See [Security](#security-considerations).
|
|
4. **Performance and robustness** — retry logic, error handling, graceful degradation.
|
|
5. **New skills** — but only broadly useful ones. See [Should it be a Skill or a Tool?](#should-it-be-a-skill-or-a-tool)
|
|
6. **New tools** — rarely needed. Most capabilities should be skills. See below.
|
|
7. **Documentation** — fixes, clarifications, new examples.
|
|
|
|
---
|
|
|
|
## Should it be a Skill or a Tool?
|
|
|
|
This is the most common question for new contributors. The answer is almost always **skill**.
|
|
|
|
### 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 managed by the agent harness
|
|
- It needs custom processing logic that must execute precisely every time (not "best effort" from LLM interpretation)
|
|
- It handles binary data, streaming, or real-time events that can't go through the terminal
|
|
- Examples: browser automation (Browserbase session management), TTS (audio encoding + platform delivery), vision analysis (base64 image handling)
|
|
|
|
### Should the Skill be bundled?
|
|
|
|
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 but is inactive by default. Users can discover it via `hermes skills browse` (labeled "official") and install it with `hermes skills install` (built-in trust path).
|
|
|
|
If your skill is specialized, community-contributed, or niche, it's better suited for a **Skills Hub** — upload it to a skills registry and share it in the [Nous Research Discord](https://discord.gg/NousResearch). Users can install it with `hermes skills install`.
|
|
|
|
---
|
|
|
|
## Memory Providers: Ship as a Standalone Plugin
|
|
|
|
**We are accepting new memory providers only as standalone plugin repos.** The set of built-in providers under `plugins/memory/` (honcho, mem0, supermemory, byterover, hindsight, holographic, openviking, retaindb) is complete. If you want to add a new memory backend, publish it as a **standalone plugin repo** that users install into `~/.hermes/plugins/` (or via a pip entry point).
|
|
|
|
Standalone memory plugins:
|
|
|
|
- Implement the same `MemoryProvider` ABC (`agent/memory_provider.py`) — `sync_turn`, `prefetch`, `shutdown`, and optionally `post_setup(hermes_home, config)` for setup-wizard integration
|
|
- Use the same discovery system — `discover_memory_providers()` picks them up from user/project plugin directories and pip entry points
|
|
- Integrate with `hermes memory setup` via `post_setup()` — no need to touch core code
|
|
- Can register their own CLI subcommands via `register_cli(subparser)` in a `cli.py` file
|
|
- Get all the same lifecycle hooks and config plumbing as in-tree providers
|
|
|
|
PRs that add a new directory under `plugins/memory/` will be closed with a pointer to publish the provider as its own repo. Existing in-tree providers stay; bug fixes to them are welcome.
|
|
|
|
Here's why standalone is the right path: memory providers are the most common plugin type and centralizing them all here creates unsustainable coupling and maintenance load.
|
|
|
|
---
|
|
|
|
## Development Setup
|
|
|
|
### Prerequisites
|
|
|
|
| Requirement | Notes |
|
|
|-------------|-------|
|
|
| **Git** | With the `git-lfs` extension installed |
|
|
| **Python 3.11+** | uv will install it if missing |
|
|
| **uv** | Fast Python package manager ([install](https://docs.astral.sh/uv/)) |
|
|
| **Node.js 20+** | Optional — needed for browser tools and WhatsApp bridge (matches root `package.json` engines) |
|
|
|
|
### Clone and install
|
|
|
|
```bash
|
|
git clone https://github.com/NousResearch/hermes-agent.git
|
|
cd hermes-agent
|
|
|
|
# Create venv with Python 3.11
|
|
uv venv venv --python 3.11
|
|
export VIRTUAL_ENV="$(pwd)/venv"
|
|
|
|
# Install with all extras (messaging, cron, CLI menus, dev tools)
|
|
uv pip install -e ".[all,dev]"
|
|
|
|
# Optional: browser tools
|
|
npm install
|
|
```
|
|
|
|
### Configure for development
|
|
|
|
```bash
|
|
mkdir -p ~/.hermes/{cron,sessions,logs,memories,skills}
|
|
cp cli-config.yaml.example ~/.hermes/config.yaml
|
|
touch ~/.hermes/.env
|
|
|
|
# Add at minimum an LLM provider key:
|
|
echo "OPENROUTER_API_KEY=***" >> ~/.hermes/.env
|
|
```
|
|
|
|
### Run
|
|
|
|
```bash
|
|
# Symlink for global access
|
|
mkdir -p ~/.local/bin
|
|
ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes
|
|
|
|
# Verify
|
|
hermes doctor
|
|
hermes chat -q "Hello"
|
|
```
|
|
|
|
### Run tests
|
|
|
|
```bash
|
|
# Preferred — matches CI (hermetic env, 4 xdist workers); see AGENTS.md
|
|
scripts/run_tests.sh
|
|
|
|
# Alternative (activate the venv first). The wrapper is still recommended
|
|
# for parity with GitHub Actions before you open a PR:
|
|
pytest tests/ -v
|
|
```
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
hermes-agent/
|
|
├── run_agent.py # AIAgent class — core conversation loop, tool dispatch, session persistence
|
|
├── cli.py # HermesCLI class — interactive TUI, prompt_toolkit integration
|
|
├── model_tools.py # Tool orchestration (thin layer over tools/registry.py)
|
|
├── toolsets.py # Tool groupings and presets (hermes-cli, hermes-telegram, etc.)
|
|
├── hermes_state.py # SQLite session database with FTS5 full-text search, session titles
|
|
├── batch_runner.py # Parallel batch processing for trajectory generation
|
|
│
|
|
├── agent/ # Agent internals (extracted modules)
|
|
│ ├── prompt_builder.py # System prompt assembly (identity, skills, context files, memory)
|
|
│ ├── context_compressor.py # Auto-summarization when approaching context limits
|
|
│ ├── auxiliary_client.py # Resolves auxiliary OpenAI clients (summarization, vision)
|
|
│ ├── display.py # KawaiiSpinner, tool progress formatting
|
|
│ ├── model_metadata.py # Model context lengths, token estimation
|
|
│ └── trajectory.py # Trajectory saving helpers
|
|
│
|
|
├── hermes_cli/ # CLI command implementations
|
|
│ ├── main.py # Entry point, argument parsing, command dispatch
|
|
│ ├── config.py # Config management, migration, env var definitions
|
|
│ ├── setup.py # Interactive setup wizard
|
|
│ ├── auth.py # Provider resolution, OAuth, Nous Portal
|
|
│ ├── models.py # OpenRouter model selection lists
|
|
│ ├── banner.py # Welcome banner, ASCII art
|
|
│ ├── commands.py # Central slash command registry (CommandDef), autocomplete, gateway helpers
|
|
│ ├── callbacks.py # Interactive callbacks (clarify, sudo, approval)
|
|
│ ├── doctor.py # Diagnostics
|
|
│ ├── skills_hub.py # Skills Hub CLI + /skills slash command
|
|
│ └── skin_engine.py # Skin/theme engine — data-driven CLI visual customization
|
|
│
|
|
├── tools/ # Tool implementations (self-registering)
|
|
│ ├── registry.py # Central tool registry (schemas, handlers, dispatch)
|
|
│ ├── approval.py # Dangerous command detection + per-session approval
|
|
│ ├── terminal_tool.py # Terminal orchestration (sudo, env lifecycle, backends)
|
|
│ ├── file_operations.py # read_file, write_file, search, patch, etc.
|
|
│ ├── web_tools.py # web_search, web_extract (Parallel/Firecrawl + Gemini summarization)
|
|
│ ├── vision_tools.py # Image analysis via multimodal models
|
|
│ ├── delegate_tool.py # Subagent spawning and parallel task execution
|
|
│ ├── code_execution_tool.py # Sandboxed Python with RPC tool access
|
|
│ ├── session_search_tool.py # Search past conversations with FTS5 + anchored windows
|
|
│ ├── cronjob_tools.py # Scheduled task management
|
|
│ ├── skill_tools.py # Skill search, load, manage
|
|
│ └── environments/ # Terminal execution backends
|
|
│ ├── base.py # BaseEnvironment ABC
|
|
│ ├── local.py, docker.py, ssh.py, singularity.py, modal.py, daytona.py
|
|
│
|
|
├── gateway/ # Messaging gateway
|
|
│ ├── run.py # GatewayRunner — platform lifecycle, message routing, cron
|
|
│ ├── config.py # Platform configuration resolution
|
|
│ ├── session.py # Session store, context prompts, reset policies
|
|
│ └── platforms/ # Platform adapters
|
|
│ ├── telegram.py, discord_adapter.py, slack.py, whatsapp.py
|
|
│
|
|
├── scripts/ # Installer and bridge scripts
|
|
│ ├── install.sh # Linux/macOS installer
|
|
│ ├── install.ps1 # Windows PowerShell installer
|
|
│ └── whatsapp-bridge/ # Node.js WhatsApp bridge (Baileys)
|
|
│
|
|
├── skills/ # Bundled skills (copied to ~/.hermes/skills/ on install)
|
|
├── optional-skills/ # Official optional skills (discoverable via hub, not activated by default)
|
|
├── tests/ # Test suite
|
|
├── website/ # Documentation site (hermes-agent.nousresearch.com)
|
|
│
|
|
├── cli-config.yaml.example # Example configuration (copied to ~/.hermes/config.yaml)
|
|
└── AGENTS.md # Development guide for AI coding assistants
|
|
```
|
|
|
|
### User configuration (stored in `~/.hermes/`)
|
|
|
|
| Path | Purpose |
|
|
|------|---------|
|
|
| `~/.hermes/config.yaml` | Settings (model, terminal, toolsets, compression, etc.) |
|
|
| `~/.hermes/.env` | API keys and secrets |
|
|
| `~/.hermes/auth.json` | OAuth credentials (Nous Portal) |
|
|
| `~/.hermes/skills/` | All active skills (bundled + hub-installed + agent-created) |
|
|
| `~/.hermes/memories/` | Persistent memory (MEMORY.md, USER.md) |
|
|
| `~/.hermes/state.db` | SQLite session database |
|
|
| `~/.hermes/sessions/` | Gateway routing index (`sessions.json`), request-dump breadcrumbs, gateway `*.jsonl` transcripts, and (optionally) per-session JSON snapshots when `sessions.write_json_snapshots: true` is set. The per-session snapshots are off by default; state.db is canonical. |
|
|
| `~/.hermes/cron/` | Scheduled job data |
|
|
| `~/.hermes/whatsapp/session/` | WhatsApp bridge credentials |
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### Core Loop
|
|
|
|
```
|
|
User message → AIAgent._run_agent_loop()
|
|
├── Build system prompt (prompt_builder.py)
|
|
├── Build API kwargs (model, messages, tools, reasoning config)
|
|
├── Call LLM (OpenAI-compatible API)
|
|
├── If tool_calls in response:
|
|
│ ├── Execute each tool via registry dispatch
|
|
│ ├── Add tool results to conversation
|
|
│ └── Loop back to LLM call
|
|
├── If text response:
|
|
│ ├── Persist session to DB
|
|
│ └── Return final_response
|
|
└── Context compression if approaching token limit
|
|
```
|
|
|
|
### Key Design Patterns
|
|
|
|
- **Self-registering tools**: Each tool file calls `registry.register()` at import time. `model_tools.py` triggers discovery by importing all tool modules.
|
|
- **Toolset grouping**: Tools are grouped into toolsets (`web`, `terminal`, `file`, `browser`, etc.) that can be enabled/disabled per platform.
|
|
- **Session persistence**: All conversations are stored in SQLite (`hermes_state.py`) with full-text search and unique session titles. Per-session JSON snapshots in `~/.hermes/sessions/` were superseded by the SQLite store and are off by default; opt back in with `sessions.write_json_snapshots: true` if you have external tooling that consumes the JSON files directly.
|
|
- **Ephemeral injection**: System prompts and prefill messages are injected at API call time only, kept out of persistent storage (never written to database or logs).
|
|
- **Provider abstraction**: The agent works with any OpenAI-compatible API. Provider resolution happens at init time (Nous Portal OAuth, OpenRouter API key, or custom endpoint).
|
|
- **Provider routing**: When using OpenRouter, `provider_routing` in config.yaml controls provider selection (sort by throughput/latency/price, allow/ignore specific providers, data retention policies). These are injected as `extra_body.provider` in API requests.
|
|
|
|
---
|
|
|
|
## Code Style
|
|
|
|
- **PEP 8** with practical exceptions (line length is not strictly enforced)
|
|
- **Comments**: Explain non-obvious intent, trade-offs, or API quirks. Skip narration of what the code already says — `# increment counter` adds nothing
|
|
- **Error handling**: Catch specific exceptions. Log with `logger.warning()`/`logger.error()` — use `exc_info=True` for unexpected errors so stack traces appear in logs
|
|
- **Cross-platform**: Write code that works on all platforms. See [Cross-Platform Compatibility](#cross-platform-compatibility)
|
|
|
|
---
|
|
|
|
## Adding a New Tool
|
|
|
|
Before writing a tool, ask: [should this be a skill instead?](#should-it-be-a-skill-or-a-tool)
|
|
|
|
Tools self-register with the central registry. Each tool file co-locates its schema, handler, and registration:
|
|
|
|
```python
|
|
"""my_tool — Brief description of what this tool does."""
|
|
|
|
import json
|
|
from tools.registry import registry
|
|
|
|
|
|
def my_tool(param1: str, param2: int = 10, **kwargs) -> str:
|
|
"""Handler. Returns a string result (often JSON)."""
|
|
result = do_work(param1, param2)
|
|
return json.dumps(result)
|
|
|
|
|
|
MY_TOOL_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "my_tool",
|
|
"description": "What this tool does and when the agent should use it.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"param1": {"type": "string", "description": "What param1 is"},
|
|
"param2": {"type": "integer", "description": "What param2 is", "default": 10},
|
|
},
|
|
"required": ["param1"],
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def _check_requirements() -> bool:
|
|
"""Return True if this tool's dependencies are available."""
|
|
return True
|
|
|
|
|
|
registry.register(
|
|
name="my_tool",
|
|
toolset="my_toolset",
|
|
schema=MY_TOOL_SCHEMA,
|
|
handler=lambda args, **kw: my_tool(**args, **kw),
|
|
check_fn=_check_requirements,
|
|
)
|
|
```
|
|
|
|
**Wire into a toolset (required):** Built-in tools are auto-discovered: any
|
|
`tools/*.py` file that contains a top-level `registry.register(...)` call is
|
|
imported by `discover_builtin_tools()` in `tools/registry.py` when `model_tools`
|
|
loads. There is **no** manual import list in `model_tools.py` to maintain.
|
|
|
|
You must still add the tool name to the appropriate list in `toolsets.py`
|
|
(for example `_HERMES_CORE_TOOLS` or a dedicated toolset); otherwise the tool
|
|
registers but is never exposed to the agent. If you introduce a new toolset,
|
|
add it in `toolsets.py` and wire it into the relevant platform presets.
|
|
|
|
See `AGENTS.md` (section **Adding New Tools**) for profile-aware paths and
|
|
plugin vs core guidance.
|
|
|
|
---
|
|
|
|
## Adding a Skill
|
|
|
|
Bundled skills live in `skills/` organized by category. Official optional skills use the same structure in `optional-skills/`:
|
|
|
|
```
|
|
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)
|
|
required_environment_variables: # Optional — secure setup-on-load metadata
|
|
- name: MY_API_KEY
|
|
prompt: API key
|
|
help: Where to get it
|
|
required_for: full functionality
|
|
prerequisites: # Optional legacy runtime requirements
|
|
env_vars: [MY_API_KEY] # Backward-compatible alias for required env vars
|
|
commands: [curl, jq] # Advisory only; does not hide the skill
|
|
metadata:
|
|
hermes:
|
|
tags: [Category, Subcategory, Keywords]
|
|
related_skills: [other-skill-name]
|
|
fallback_for_toolsets: [web] # Optional — show only when toolset is unavailable
|
|
requires_toolsets: [terminal] # Optional — show only when toolset is available
|
|
---
|
|
|
|
# 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 declare which OS platforms they support via the `platforms` frontmatter field. Skills with this field are automatically hidden from the system prompt, `skills_list()`, and slash commands on incompatible platforms.
|
|
|
|
```yaml
|
|
platforms: [macos] # macOS only (e.g., iMessage, Apple Reminders)
|
|
platforms: [macos, linux] # macOS and Linux
|
|
platforms: [windows] # Windows only
|
|
```
|
|
|
|
If the field is omitted or empty, the skill loads on all platforms (backward compatible). See `skills/apple/` for examples of macOS-only skills.
|
|
|
|
### Conditional skill activation
|
|
|
|
Skills can declare conditions that control when they appear in the system prompt, based on which tools and toolsets are available in the current session. This is primarily used for **fallback skills** — alternatives that should only be shown when a primary tool is unavailable.
|
|
|
|
Four fields are supported under `metadata.hermes`:
|
|
|
|
```yaml
|
|
metadata:
|
|
hermes:
|
|
fallback_for_toolsets: [web] # Show ONLY when these toolsets are unavailable
|
|
requires_toolsets: [terminal] # Show ONLY when these toolsets are available
|
|
fallback_for_tools: [web_search] # Show ONLY when these specific tools are unavailable
|
|
requires_tools: [terminal] # Show ONLY when these specific tools are available
|
|
```
|
|
|
|
**Semantics:**
|
|
- `fallback_for_*`: The skill is a backup. It is **hidden** when the listed tools/toolsets are available, and **shown** when they are unavailable. Use this for free alternatives to premium tools.
|
|
- `requires_*`: The skill needs certain tools to function. It is **hidden** when the listed tools/toolsets are unavailable. Use this for skills that depend on specific capabilities (e.g., a skill that only makes sense with terminal access).
|
|
- If both are specified, both conditions must be satisfied for the skill to appear.
|
|
- If neither is specified, the skill is always shown (backward compatible).
|
|
|
|
**Examples:**
|
|
|
|
```yaml
|
|
# DuckDuckGo search — shown when Firecrawl (web toolset) is unavailable
|
|
metadata:
|
|
hermes:
|
|
fallback_for_toolsets: [web]
|
|
|
|
# Smart home skill — only useful when terminal is available
|
|
metadata:
|
|
hermes:
|
|
requires_toolsets: [terminal]
|
|
|
|
# Local browser fallback — shown when Browserbase is unavailable
|
|
metadata:
|
|
hermes:
|
|
fallback_for_toolsets: [browser]
|
|
```
|
|
|
|
The filtering happens at prompt build time in `agent/prompt_builder.py`. The `build_skills_system_prompt()` function receives the set of available tools and toolsets from the agent and uses `_skill_should_show()` to evaluate each skill's conditions.
|
|
|
|
### Skill setup metadata
|
|
|
|
Skills can declare secure setup-on-load metadata via the `required_environment_variables` frontmatter field. Missing values do not hide the skill from discovery; they trigger a CLI-only secure prompt when the skill is actually loaded.
|
|
|
|
```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 may skip setup and keep loading the skill. Hermes only exposes metadata (`stored_as`, `skipped`, `validated`) to the model — never the secret value.
|
|
|
|
Legacy `prerequisites.env_vars` remains supported and is normalized into the new representation.
|
|
|
|
```yaml
|
|
prerequisites:
|
|
env_vars: [TENOR_API_KEY] # Legacy alias for required_environment_variables
|
|
commands: [curl, jq] # Advisory CLI checks
|
|
```
|
|
|
|
Gateway and messaging sessions never collect secrets in-band; they instruct the user to run `hermes setup` or update `~/.hermes/.env` locally.
|
|
|
|
**When to declare required environment variables:**
|
|
- The skill uses an API key or token that should be collected securely at load time
|
|
- The skill can still be useful if the user skips setup, but may degrade gracefully
|
|
|
|
**When declaring command prerequisites:**
|
|
- The skill relies on a CLI tool that may not be installed (e.g., `himalaya`, `openhue`, `ddgs`)
|
|
- Treat command checks as guidance, not discovery-time hiding
|
|
|
|
See `skills/gifs/gif-search/` and `skills/email/himalaya/` for examples.
|
|
|
|
### Skill authoring standards (HARDLINE)
|
|
|
|
Every new or modernized skill — bundled, optional, or contributed — must meet these standards before merge. Reviewers reject PRs that violate them.
|
|
|
|
1. **`description` ≤ 60 characters, one sentence, ends with a period.** Long descriptions bloat the skill listing UI and dilute the model's attention when many skills are loaded. State the capability, not the implementation. Use precise, functional language — skip marketing words ("powerful", "comprehensive", "seamless", "advanced"). State the capability uniquely without repeating the skill name. Verify with:
|
|
```python
|
|
import re, pathlib
|
|
m = re.search(r'^description: (.*)$',
|
|
pathlib.Path('skills/<cat>/<name>/SKILL.md').read_text(),
|
|
re.MULTILINE)
|
|
assert len(m.group(1)) <= 60, len(m.group(1))
|
|
```
|
|
|
|
Good: `Search arXiv papers by keyword, author, category, or ID.`
|
|
Bad: `A powerful and comprehensive skill that allows the agent to search arXiv for relevant academic papers using various criteria including keywords, authors, and categories.`
|
|
|
|
2. **Tools referenced in SKILL.md prose must be native Hermes tools or MCP servers the skill explicitly expects.** When the skill needs a capability, point at the proper tool by name in backticks: `` `terminal` ``, `` `web_extract` ``, `` `web_search` ``, `` `read_file` ``, `` `write_file` ``, `` `patch` ``, `` `search_files` ``, `` `vision_analyze` ``, `` `browser_navigate` ``, `` `delegate_task` ``, `` `image_generate` ``, `` `text_to_speech` ``, `` `cronjob` ``, `` `memory` ``, `` `skill_view` ``, `` `todo` ``, `` `execute_code` ``.
|
|
|
|
Do NOT name shell utilities the agent already has wrapped:
|
|
|
|
| Don't say | Say |
|
|
|---|---|
|
|
| `grep`, `rg` | `search_files` |
|
|
| `cat`, `head`, `tail` | `read_file` |
|
|
| `sed`, `awk` | `patch` |
|
|
| `find`, `ls` | `search_files` (with `target='files'`) |
|
|
| `curl` for content extraction | `web_extract` |
|
|
| `echo > file`, `cat <<EOF` | `write_file` |
|
|
|
|
If the skill depends on an MCP server, name the MCP server and document its setup in `## Prerequisites`. Third-party CLIs (e.g. `ffmpeg`, `gh`, a specific SDK) are fine to invoke from inside script files, but the prose should frame the interaction as "invoke through the `terminal` tool", not as a manual shell session.
|
|
|
|
3. **`platforms:` gating audited against actual script imports.** Skills that use POSIX-only primitives (`fcntl`, `termios`, `os.setsid`, `os.kill(pid, 0)` for liveness, `/proc`, hardcoded `/tmp` paths, `signal.SIGKILL`, bash heredocs, `osascript`, `apt`, `systemctl`) must declare their supported platforms via the `platforms:` frontmatter. Default posture is to make it cross-platform first — `tempfile.gettempdir()`, `pathlib.Path`, `psutil.pid_exists()`, Python-level filtering instead of `grep`. Gate to a narrower set only when the dependency is genuinely platform-bound (e.g. `osascript` is macOS-only, `/proc` is Linux-only).
|
|
|
|
4. **`author` credits the human contributor first.** For external contributions, the contributor's real name + GitHub handle goes first (`Jane Doe (jane-doe)`); "Hermes Agent" is the secondary collaborator. If the contributor's commit shows "Hermes Agent" as author because they used Hermes to draft the skill, replace it with their actual name — credit the human, not the tool.
|
|
|
|
5. **SKILL.md body uses the modern section order.** `# <Skill> Skill` title, 2-3 sentence intro stating what it does and its scope boundaries, then:
|
|
- `## When to Use` — trigger conditions
|
|
- `## Prerequisites` — env vars, install steps, MCP setup, API key sourcing
|
|
- `## How to Run` — canonical invocation through the `terminal` tool
|
|
- `## Quick Reference` — flat command/API reference
|
|
- `## Procedure` — numbered steps with copy-paste commands
|
|
- `## Pitfalls` — known limits, rate limits, things that look broken but aren't
|
|
- `## Verification` — single command that proves the skill works
|
|
|
|
Target ~200 lines for a complex skill, ~100 lines for a simple one. Cut redundant intro fluff, marketing prose, and re-explanations of env vars already documented in `## Prerequisites`.
|
|
|
|
6. **Scripts go in `scripts/`, references in `references/`, templates in `templates/`.** Ship helper scripts instead of expecting the model to inline-write parsers, XML walkers, or non-trivial logic every call. Reference scripts from SKILL.md by path relative to the skill directory.
|
|
|
|
7. **Tests live at `tests/skills/test_<skill>_skill.py`** and use only stdlib + pytest + `unittest.mock`. No live network calls. Run via `scripts/run_tests.sh tests/skills/test_<skill>_skill.py -q`. Must pass under the hermetic CI env (no API keys leaking through). Use `monkeypatch` and `tmp_path` for any env-var or filesystem dependencies.
|
|
|
|
8. **`.env.example` additions are isolated to a clearly delimited block.** Keep changes within the skill's own delimited block — contributor-supplied `.env.example` versions are usually stale, and edits outside the skill's own block will be dropped during salvage. Comment all values with `#` (it's documentation, not live config).
|
|
|
|
### Skill guidelines
|
|
|
|
- **Prefer Hermes built-in tools over external dependencies.** Use stdlib Python, curl, and existing Hermes tools (`web_extract`, `terminal`, `read_file`). Add external deps only when necessary.
|
|
- **Progressive disclosure.** Put the most common workflow first. Edge cases and advanced usage go at the bottom.
|
|
- **Include helper scripts** for XML/JSON parsing or complex logic — ship scripts so models don't need to inline-write parsers every time.
|
|
- **Test it.** Run `hermes --toolsets skills -q "Use the X skill to do Y"` and verify the agent follows the instructions correctly.
|
|
|
|
---
|
|
|
|
## Adding a Skin / Theme
|
|
|
|
Hermes uses a data-driven skin system — no code changes needed to add a new skin.
|
|
|
|
**Option A: User skin (YAML file)**
|
|
|
|
Create `~/.hermes/skins/<name>.yaml`:
|
|
|
|
```yaml
|
|
name: mytheme
|
|
description: Short description of the theme
|
|
|
|
colors:
|
|
banner_border: "#HEX" # Panel border color
|
|
banner_title: "#HEX" # Panel title color
|
|
banner_accent: "#HEX" # Section header color
|
|
banner_dim: "#HEX" # Muted/dim text color
|
|
banner_text: "#HEX" # Body text color
|
|
response_border: "#HEX" # Response box border
|
|
|
|
spinner:
|
|
waiting_faces: ["(⚔)", "(⛨)"]
|
|
thinking_faces: ["(⚔)", "(⌁)"]
|
|
thinking_verbs: ["forging", "plotting"]
|
|
wings: # Optional left/right decorations
|
|
- ["⟪⚔", "⚔⟫"]
|
|
|
|
branding:
|
|
agent_name: "My Agent"
|
|
welcome: "Welcome message"
|
|
response_label: " ⚔ Agent "
|
|
prompt_symbol: "⚔"
|
|
|
|
tool_prefix: "╎" # Tool output line prefix
|
|
```
|
|
|
|
All fields are optional — missing values inherit from the default skin.
|
|
|
|
**Option B: Built-in skin**
|
|
|
|
Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`. Use the same schema as above but as a Python dict. Built-in skins ship with the package and are always available.
|
|
|
|
**Activating:**
|
|
- CLI: `/skin mytheme` or set `display.skin: mytheme` in config.yaml
|
|
- Config: `display: { skin: mytheme }`
|
|
|
|
See `hermes_cli/skin_engine.py` for the full schema and existing skins as examples.
|
|
|
|
---
|
|
|
|
## Cross-Platform Compatibility
|
|
|
|
Hermes runs on Linux, macOS, and native Windows (plus WSL2). When writing code
|
|
that touches the OS, assume *any* platform can hit your code path.
|
|
|
|
> **Before you PR:** run `scripts/check-windows-footguns.py` to catch the
|
|
> common Windows-unsafe patterns in your diff. It's grep-based and cheap;
|
|
> CI runs it on every PR too.
|
|
|
|
### Critical rules
|
|
|
|
1. **Use `psutil.pid_exists()` instead of `os.kill(pid, 0)` for liveness checks.** `os.kill(pid, 0)`
|
|
is a standard POSIX idiom to check "is this PID alive" — the signal 0
|
|
is a no-op permission check. **On Windows it is NOT a no-op.** Python's
|
|
Windows `os.kill` maps `sig=0` to `CTRL_C_EVENT` (they collide at the
|
|
integer value 0) and routes it through `GenerateConsoleCtrlEvent(0, pid)`,
|
|
which broadcasts Ctrl+C to the **entire console process group** containing
|
|
the target PID. "Probe if alive" silently becomes "kill the target and
|
|
often unrelated processes sharing its console." See [bpo-14484](https://bugs.python.org/issue14484)
|
|
(open since 2012 — will never be fixed for compat reasons).
|
|
|
|
**Preferred:** use `psutil` (a core dependency — always available):
|
|
|
|
```python
|
|
import psutil
|
|
if psutil.pid_exists(pid):
|
|
# process is alive — safe on every platform
|
|
...
|
|
```
|
|
|
|
If you specifically need the hermes wrapper (it has a stdlib fallback
|
|
for scaffold-phase imports before pip install finishes), use
|
|
`gateway.status._pid_exists(pid)`. It calls `psutil.pid_exists` first
|
|
and falls back to a hand-rolled `OpenProcess + WaitForSingleObject`
|
|
dance on Windows only when psutil is somehow missing.
|
|
|
|
Audit grep for new callsites: `rg "os\.kill\([^,]+,\s*0\s*\)"`. Any hit
|
|
in non-test code is presumptively a Windows silent-kill bug.
|
|
|
|
2. **Use `shutil.which()` before shelling out — validate tool availability on all platforms.** `wmic` was removed in Windows 10 21H1 and later. `ps`,
|
|
`kill`, `grep`, `awk`, `fuser`, `lsof`, `pgrep`, and most POSIX CLI tools
|
|
are Unix-only. Test availability with
|
|
`shutil.which("tool")` and fall back to a Windows-native equivalent —
|
|
usually PowerShell via `subprocess.run(["powershell", "-NoProfile",
|
|
"-Command", ...])`.
|
|
|
|
For process enumeration: PowerShell's `Get-CimInstance Win32_Process` is
|
|
the modern replacement for `wmic process`. See
|
|
`hermes_cli/gateway.py::_scan_gateway_pids` for the pattern.
|
|
|
|
3. **`termios` and `fcntl` are Unix-only.** Always catch both `ImportError`
|
|
and `NotImplementedError`:
|
|
```python
|
|
try:
|
|
from simple_term_menu import TerminalMenu
|
|
menu = TerminalMenu(options)
|
|
idx = menu.show()
|
|
except (ImportError, NotImplementedError):
|
|
# Fallback: numbered menu for Windows
|
|
for i, opt in enumerate(options):
|
|
print(f" {i+1}. {opt}")
|
|
idx = int(input("Choice: ")) - 1
|
|
```
|
|
|
|
4. **File encoding.** Windows may save `.env` files in `cp1252`. Always
|
|
handle encoding errors:
|
|
```python
|
|
try:
|
|
load_dotenv(env_path)
|
|
except UnicodeDecodeError:
|
|
load_dotenv(env_path, encoding="latin-1")
|
|
```
|
|
Config files (`config.yaml`) may be saved with a UTF-8 BOM by Notepad and
|
|
similar editors — use `encoding="utf-8-sig"` when reading files that
|
|
could have been touched by a Windows GUI editor.
|
|
|
|
5. **Process management.** `os.setsid()`, `os.killpg()`, `os.fork()`,
|
|
`os.getuid()`, and POSIX signal handling differ on Windows. Guard with
|
|
`platform.system()`, `sys.platform`, or `hasattr(os, "setsid")`:
|
|
```python
|
|
if platform.system() != "Windows":
|
|
kwargs["preexec_fn"] = os.setsid
|
|
else:
|
|
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
```
|
|
|
|
**Preferred:** for killing a process AND its children (what `os.killpg`
|
|
does on POSIX), use `psutil` — it works on every platform:
|
|
```python
|
|
import psutil
|
|
try:
|
|
parent = psutil.Process(pid)
|
|
# Kill children first (leaf-up), then the parent.
|
|
for child in parent.children(recursive=True):
|
|
child.kill()
|
|
parent.kill()
|
|
except psutil.NoSuchProcess:
|
|
pass
|
|
```
|
|
|
|
6. **Signals that are Unix-only — `SIGALRM`, `SIGCHLD`, `SIGHUP`,
|
|
`SIGUSR1`, `SIGUSR2`, `SIGPIPE`, `SIGQUIT`, `SIGKILL`.** Python's
|
|
`signal` module raises `AttributeError` at import time if you reference
|
|
them on Windows. Use `getattr(signal, "SIGKILL", signal.SIGTERM)` or
|
|
gate the whole block behind a platform check. `loop.add_signal_handler`
|
|
raises `NotImplementedError` on Windows — always catch it.
|
|
|
|
7. **Path separators.** Use `pathlib.Path` instead of string concatenation
|
|
with `/`. Forward slashes work almost everywhere on Windows, but
|
|
`subprocess.run(["cmd.exe", "/c", ...])` and other shell contexts can
|
|
require backslashes — convert with `str(path)` at the subprocess boundary,
|
|
not inside Python logic.
|
|
|
|
8. **Symlinks need elevated privileges on Windows** (unless Developer Mode is
|
|
on). Tests that create symlinks need `@pytest.mark.skipif(sys.platform ==
|
|
"win32", reason="Symlinks require elevated privileges on Windows")`.
|
|
|
|
9. **POSIX file modes (0o600, 0o644, etc.) are NOT enforced on NTFS** by
|
|
default. Tests that assert on `stat().st_mode & 0o777` must skip on
|
|
Windows — the concept doesn't translate. Use ACLs (`icacls`, `pywin32`)
|
|
for Windows secret-file protection if needed.
|
|
|
|
10. **Detached background daemons on Windows need `pythonw.exe`, NOT
|
|
`python.exe`.** `python.exe` always allocates or attaches to a console,
|
|
which makes it vulnerable to `CTRL_C_EVENT` broadcasts from any sibling
|
|
process. `pythonw.exe` is the no-console variant. Combine with
|
|
`CREATE_NO_WINDOW | DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP |
|
|
CREATE_BREAKAWAY_FROM_JOB` in `subprocess.Popen(creationflags=...)`.
|
|
See `hermes_cli/gateway_windows.py::_spawn_detached` for the reference
|
|
implementation.
|
|
|
|
11. **`subprocess.Popen` with `.cmd` or `.bat` shims needs `shutil.which`
|
|
to resolve.** Passing `"agent-browser"` to `Popen` on Windows finds
|
|
the extensionless POSIX shebang shim in `node_modules/.bin/`, which
|
|
`CreateProcessW` can't execute — you'll get `WinError 193 "not a valid
|
|
Win32 application"`. Use `shutil.which("agent-browser", path=local_bin)`
|
|
which honors PATHEXT and picks the `.CMD` variant on Windows.
|
|
|
|
12. **Invoke Python explicitly instead of shell shebangs.** `#!/usr/bin/env
|
|
python` only works when the file is executed through a Unix shell.
|
|
`subprocess.run(["./myscript.py"])` on Windows fails even if the file
|
|
has a shebang line. Always invoke Python explicitly:
|
|
`[sys.executable, "myscript.py"]`.
|
|
|
|
13. **Shell commands in installers.** If you change `scripts/install.sh`,
|
|
make the equivalent change in `scripts/install.ps1`. The two scripts
|
|
are the canonical example of "works on Linux does not mean works on
|
|
Windows" and have drifted multiple times — keep them in lockstep.
|
|
|
|
14. **Known paths that are OneDrive-redirected on Windows:** Desktop,
|
|
Documents, Pictures, Videos. The "real" path when OneDrive Backup is
|
|
enabled is `%USERPROFILE%\OneDrive\Desktop` (etc.), NOT
|
|
`%USERPROFILE%\Desktop` (which exists as an empty husk). Resolve the
|
|
real location via `ctypes` + `SHGetKnownFolderPath` or by reading the
|
|
`Shell Folders` registry key — resolve the real location instead of
|
|
assuming `~/Desktop`.
|
|
|
|
15. **CRLF vs LF in generated scripts.** Windows `cmd.exe` and `schtasks`
|
|
parse line-by-line; mixed or LF-only line endings can break multi-line
|
|
`.cmd` / `.bat` files. Use `open(path, "w", encoding="utf-8",
|
|
newline="\r\n")` — or `open(path, "wb")` + explicit bytes — when
|
|
generating scripts Windows will execute.
|
|
|
|
16. **Two different quoting schemes in one command line.** `subprocess.run
|
|
(["schtasks", "/TR", some_cmd])` → schtasks itself parses `/TR`, AND
|
|
the `some_cmd` string is re-parsed by `cmd.exe` when the task fires.
|
|
Different parsers, different escape rules. Use two separate quoting
|
|
helpers and never cross them. See `hermes_cli/gateway_windows.py::
|
|
_quote_cmd_script_arg` and `_quote_schtasks_arg` for the reference
|
|
pair.
|
|
|
|
### Testing cross-platform
|
|
|
|
Tests that use POSIX-only syscalls need a skip marker. Common ones:
|
|
- Symlinks → `@pytest.mark.skipif(sys.platform == "win32", ...)`
|
|
- `0o600` file modes → `@pytest.mark.skipif(sys.platform.startswith("win"), ...)`
|
|
- `signal.SIGALRM` → Unix-only (see `tests/conftest.py::_enforce_test_timeout`)
|
|
- `os.setsid` / `os.fork` → Unix-only
|
|
- Live Winsock / Windows-specific regression tests →
|
|
`@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific regression")`
|
|
|
|
If you monkeypatch `sys.platform` for cross-platform tests, also patch
|
|
`platform.system()` / `platform.release()` / `platform.mac_ver()` — each
|
|
re-reads the real OS independently, so half-patched tests still route
|
|
through the wrong branch on a Windows runner.
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
Hermes has terminal access. Security matters.
|
|
|
|
### Existing protections
|
|
|
|
| Layer | Implementation |
|
|
|-------|---------------|
|
|
| **Sudo password piping** | Uses `shlex.quote()` to prevent shell injection |
|
|
| **Dangerous command detection** | Regex patterns in `tools/approval.py` with user approval flow |
|
|
| **Cron prompt injection** | Scanner in `tools/cronjob_tools.py` blocks instruction-override patterns |
|
|
| **Write deny list** | Protected paths (`~/.ssh/authorized_keys`, `/etc/shadow`) resolved via `os.path.realpath()` to prevent symlink bypass |
|
|
| **Skills guard** | Security scanner for hub-installed skills (`tools/skills_guard.py`) |
|
|
| **Code execution sandbox** | `execute_code` child process runs with API keys stripped from environment |
|
|
| **Container hardening** | Docker: all capabilities dropped, no privilege escalation, PID limits, size-limited tmpfs |
|
|
|
|
### When contributing security-sensitive code
|
|
|
|
- **Always use `shlex.quote()`** when interpolating user input into shell commands
|
|
- **Resolve symlinks** with `os.path.realpath()` before path-based access control checks
|
|
- **Keep secrets out of logs.** API keys, tokens, and passwords must always be redacted from log output
|
|
- **Catch broad exceptions** around tool execution so a single failure doesn't crash the agent loop
|
|
- **Test on all platforms** if your change touches file paths, process management, or shell commands
|
|
|
|
If your PR affects security, note it explicitly in the description.
|
|
|
|
### Dependency pinning policy (supply chain hardening)
|
|
|
|
After the [litellm supply chain compromise](https://github.com/BerriAI/litellm/issues/24512) in March 2026 and the [Mini Shai-Hulud worm campaign](https://socket.dev/blog/tanstack-npm-packages-compromised-mini-shai-hulud-supply-chain-attack) in May 2026, all dependencies must follow these rules:
|
|
|
|
| Source type | Required treatment | Rationale |
|
|
|---|---|---|
|
|
| **PyPI package** | `>=floor,<next_major` | PyPI versions are immutable once published, but new versions can be pushed into your range. A `<next_major` ceiling stops a 1.x install from upgrading to a malicious 2.0.0. |
|
|
| **Git URL** (atroposlib, tinker, yc-bench, Baileys) | Full commit SHA | Branches and tags are mutable refs; SHA is content-addressed. |
|
|
| **GitHub Actions** | Full commit SHA + version comment | Action tags are mutable refs (e.g. tj-actions/changed-files March 2025). Pin as `uses: owner/action@<sha> # vX.Y.Z` |
|
|
| **CI-only pip installs** | `==exact` | Hermetic CI builds; churn is acceptable. |
|
|
|
|
**Every new PyPI dependency in a PR must have a `<next_major` upper bound.** PRs with unbounded `>=X.Y.Z` specs will be rejected by reviewers. The `supply-chain-audit.yml` CI workflow also flags dependency manifest changes for manual review.
|
|
|
|
**How to determine the ceiling:**
|
|
- If the package is at version `1.x.y`, use `<2`.
|
|
- If the package is at version `0.x.y` (pre-1.0), use `<0.(current_minor + 2)` — e.g. if current is `0.29.x`, use `<0.32`. This gives ~2 minor versions of headroom while keeping the window small enough that a hostile takeover version is unlikely to land inside it.
|
|
- Exception: packages with very stable APIs (e.g. `aiohttp-socks`) can use `<1` at reviewer discretion.
|
|
|
|
**Examples:**
|
|
```toml
|
|
# ✅ Correct — post-1.0
|
|
"openai>=2.21.0,<3"
|
|
"pydantic>=2.12.5,<3"
|
|
|
|
# ✅ Correct — pre-1.0 (tight minor window)
|
|
"asyncpg>=0.29,<0.32"
|
|
"aiosqlite>=0.20,<0.23"
|
|
"hindsight-client>=0.4.22,<0.5"
|
|
|
|
# ❌ Rejected — no upper bound
|
|
"some-package>=1.2.3"
|
|
|
|
# ❌ Rejected — too tight (blocks legitimate patches)
|
|
"some-package==1.2.3"
|
|
|
|
# ❌ Rejected — too loose for pre-1.0 (allows 80 minor versions)
|
|
"some-package>=0.20,<1"
|
|
```
|
|
|
|
**Reference PRs:** #2796 (litellm removal), #2810 (upper bounds pass), #9801 (SHA pinning + supply-chain-audit CI).
|
|
|
|
---
|
|
|
|
## Pull Request Process
|
|
|
|
### Branch naming
|
|
|
|
```
|
|
fix/description # Bug fixes
|
|
feat/description # New features
|
|
docs/description # Documentation
|
|
test/description # Tests
|
|
refactor/description # Code restructuring
|
|
```
|
|
|
|
### Before submitting
|
|
|
|
1. **Run tests**: `scripts/run_tests.sh` (recommended; same as CI) or `pytest tests/ -v` with the project venv activated
|
|
2. **Test manually**: Run `hermes` and exercise the code path you changed
|
|
3. **Check cross-platform impact**: If you touch file I/O, process management, or terminal handling, consider macOS, Linux, and WSL2
|
|
4. **Keep PRs focused**: One logical change per PR. Keep bug fixes, refactors, and new features in separate PRs.
|
|
|
|
### PR description
|
|
|
|
Include:
|
|
- **What** changed and **why**
|
|
- **How to test** it (reproduction steps for bugs, usage examples for features)
|
|
- **What platforms** you tested on
|
|
- Reference any related issues
|
|
|
|
### Commit messages
|
|
|
|
We use [Conventional Commits](https://www.conventionalcommits.org/):
|
|
|
|
```
|
|
<type>(<scope>): <description>
|
|
```
|
|
|
|
| Type | Use for |
|
|
|------|---------|
|
|
| `fix` | Bug fixes |
|
|
| `feat` | New features |
|
|
| `docs` | Documentation |
|
|
| `test` | Tests |
|
|
| `refactor` | Code restructuring (no behavior change) |
|
|
| `chore` | Build, CI, dependency updates |
|
|
|
|
Scopes: `cli`, `gateway`, `tools`, `skills`, `agent`, `install`, `whatsapp`, `security`, etc.
|
|
|
|
Examples:
|
|
```
|
|
fix(cli): prevent crash in save_config_value when model is a string
|
|
feat(gateway): add WhatsApp multi-user session isolation
|
|
fix(security): prevent shell injection in sudo password piping
|
|
test(tools): add unit tests for file_operations
|
|
```
|
|
|
|
---
|
|
|
|
## Reporting Issues
|
|
|
|
- Use [GitHub Issues](https://github.com/NousResearch/hermes-agent/issues)
|
|
- Include: OS, Python version, Hermes version (`hermes version`), full error traceback
|
|
- Include steps to reproduce
|
|
- Check existing issues before creating duplicates
|
|
- For security vulnerabilities, please report privately
|
|
|
|
---
|
|
|
|
## Community
|
|
|
|
- **Discord**: [discord.gg/NousResearch](https://discord.gg/NousResearch) — for questions, showcasing projects, and sharing skills
|
|
- **GitHub Discussions**: For design proposals and architecture discussions
|
|
- **Skills Hub**: Upload specialized skills to a registry and share them with the community
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|