Skip to main content
Contributions are welcome. This page covers everything you need: setting up a development environment, running tests, code style, and how to get a 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 — Windows, macOS, different Linux distros, different terminal emulators.
  3. Security hardening — shell injection, prompt injection, path traversal, privilege escalation.
  4. Performance and robustness — retry logic, error handling, graceful degradation.
  5. New skills — broadly useful ones only.
  6. New tools — rarely needed. Most new capabilities should be skills.
  7. Documentation — fixes, clarifications, new examples.

Development setup

1

Clone with submodules

mini-swe-agent is a required terminal backend. The --recurse-submodules flag during clone handles it automatically, but the explicit submodule update is useful after pulling new commits.
2

Create a virtual environment

Python 3.11 is required. uv will download it if it’s not installed.
Always activate the virtual environment before running any Python commands. The source .venv/bin/activate step is mandatory — running with a system Python will lead to import errors and difficult-to-debug failures.
3

Install dependencies

The [all,dev] extras install messaging platform libraries, CLI menus, cron dependencies, and development tools. mini-swe-agent is installed as an editable package so local changes take effect immediately.
4

Configure for development

5

Verify the installation

hermes doctor checks all configured providers, tools, and dependencies. Fix any warnings before starting development work.

Running tests

Always run the full suite before pushing changes. The suite is parallelized with pytest-xdist and completes in about 3 minutes.

Code style

  • PEP 8 with practical exceptions (no strict line length enforcement)
  • Comments — only when explaining non-obvious intent, trade-offs, or API quirks. Don’t narrate what the code does.
  • Error handling — catch specific exceptions. Log with logger.warning() / logger.error(), use exc_info=True for unexpected errors
  • Cross-platform — never assume Unix. Use pathlib.Path for paths, guard termios/fcntl imports, test process management on macOS and Windows

PR process

Branch naming

Before submitting

  1. Run python -m pytest tests/ -q and ensure all tests pass
  2. Run hermes manually and exercise the code path you changed
  3. Consider cross-platform impact for any changes touching file I/O, process management, or terminal handling
  4. Keep PRs focused — one logical change per PR

Commit messages

We use Conventional Commits:
Examples:

Known pitfalls

Do not use simple_term_menu for interactive menus. It has rendering bugs in tmux/iTerm2 — ghosting on scroll. Use curses (stdlib) instead. See hermes_cli/tools_config.py for the established pattern.
Do not use \033[K (ANSI erase-to-EOL) in spinner or display code. It leaks as literal ?[K text under prompt_toolkit’s patch_stdout. Use space-padding instead: f"\r{line}{' ' * pad}".
Do not break prompt caching. Never implement changes that alter past context, change toolsets, or rebuild the system prompt mid-conversation. Cache-breaking forces dramatically higher costs. The only legitimate exception is context compression.
Tests must not write to ~/.hermes/. The _isolate_hermes_home autouse fixture in tests/conftest.py redirects HERMES_HOME to a temp directory for every test. Never hardcode ~/.hermes/ paths in test code — they will escape isolation and corrupt the real config.

_last_resolved_tool_names is a process-global

_last_resolved_tool_names in model_tools.py is a process-global list. _run_single_child() in delegate_tool.py saves and restores this global around subagent execution. If you add code that reads this global, be aware it may be temporarily stale during child agent runs.

Security considerations

Hermes has terminal access. 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
  • Don’t log secrets — API keys, tokens, and passwords must never appear in 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 PR description.

Community