Skip to content

feat: add Hermes (NousResearch hermes-agent) preset and hook installer#1300

Open
Krishnachaitanyakc wants to merge 2 commits into
git-ai-project:mainfrom
Krishnachaitanyakc:feat/hermes-support-1136
Open

feat: add Hermes (NousResearch hermes-agent) preset and hook installer#1300
Krishnachaitanyakc wants to merge 2 commits into
git-ai-project:mainfrom
Krishnachaitanyakc:feat/hermes-support-1136

Conversation

@Krishnachaitanyakc
Copy link
Copy Markdown
Contributor

@Krishnachaitanyakc Krishnachaitanyakc commented May 7, 2026

Summary

Adds first-class support for Hermes — NousResearch's Python AI coding agent — against the post-rewrite agent-preset framework. Closes #1136.

Hermes has three hook systems (gateway hooks, plugin hooks, shell hooks). Of these, only the shell-hook system is language-agnostic and integrable from outside the Hermes process; we install into that.

Preset (presets/hermes.rs)

  • Pure parser implementing AgentPreset::parse. Handles pre_tool_call / post_tool_call for write_file, patch, and terminal.
  • Strict event handling: lifecycle events (pre_llm_call, post_llm_call, pre_api_request, post_api_request, on_session_start, on_session_end, on_session_finalize, on_session_reset, subagent_stop, transform_tool_result) return PresetError rather than fabricating a phantom file-edit checkpoint.
  • transcript_source: None for v1 — Hermes' shell-hook payload doesn't surface a transcript path, and the on-disk session storage schema under ~/.hermes/ is not part of the public hook contract; a dedicated reader can land in a follow-up.
  • agent_id.model defaults to "unknown" since the shell-hook payload doesn't carry the active model (it lives only in the LLM-call payload, not the tool-call payload).

Installer (mdm/agents/hermes.rs)

  • Writes [[hooks.<event>]] entries into ~/.hermes/config.yaml per the upstream YAML schema. Uses the documented ".*" matcher (regex match-all).
  • hooks_auto_accept: true — set at the root so our installed entries don't trigger Hermes' first-use consent prompt. Only set when the user hasn't explicitly chosen otherwise (an explicit false is preserved).
  • Idempotent: repeat install is a no-op (compared structurally so YAML round-trip whitespace doesn't cause spurious diffs).
  • Token-based ownership matching (is_git_ai_hermes_command): a hypothetical sibling preset like hermes-pro is never misidentified.
  • Uninstall removes only hermes entries; user-defined hooks survive. Empty event keys (pre_tool_call: []) and the empty hooks: block are removed entirely.

Tool classification (bash_tool.rs)

  • New Agent::Hermes variant: write_file / patchFileEdit, terminalBash, all else → Skip.

New dependency

  • serde_yml = "0.0.12" for parsing ~/.hermes/config.yaml. Hermes is the first supported agent that uses YAML config; serde_yml is a small, actively-maintained YAML crate.

Wiring

  • presets/mod.rs::resolve_preset accepts hermes.
  • mdm/agents/mod.rs::get_all_installers includes HermesInstaller. Detection uses binary_exists for all three documented entry points (hermes, hermes-agent, hermes-acp) plus the ~/.hermes dotfile fallback.
  • git_ai_handlers help text lists hermes.
  • AI_AUTHOR_NAMES and is_known_checkpoint_preset learn hermes.

Test plan

  • cargo fmt --check clean
  • cargo clippy --all-targets -- -D warnings clean (no #[allow(...)] annotations added)
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps clean
  • 11 preset unit tests (presets::hermes::tests::*) — pre/post for write_file/patch/terminal, lifecycle event rejection (10 events), unsupported tool rejection (read_file/search_files), missing field errors (session_id, cwd), invalid JSON, absolute path passthrough, tool_call_id extraction from extra dict
  • 20 installer unit tests (mdm::agents::hermes::tests::*) — fresh install (s1), idempotency (s2), preserves unrelated hooks and other settings (s3), updates outdated path (s4), dedup (s5), dry-run (s6), creates parent dir (s7), respects user's hooks_auto_accept: false (s8); uninstall variants u1-u5 including emptied-event-key removal (u4); error handling e1-e4 (invalid YAML, root-must-be-mapping, hooks-must-be-mapping); token-precise ownership matching
  • 9 integration tests (tests/integration/hermes.rs) — preset routing, lifecycle/tool rejection sweeps, 3 E2E flows asserting assert_lines_and_blame and session metadata
  • Full lib test suite passes locally on macOS in daemon mode

Open in Devin Review

Adds first-class support for Hermes' shell-hook protocol against the
post-rewrite agent-preset framework.

Preset (`presets/hermes.rs`):
  - Pure parser implementing `AgentPreset::parse`. Handles
    `pre_tool_call` / `post_tool_call` for `write_file`, `patch`, and
    `terminal`.
  - Strict event handling: lifecycle events (`pre_llm_call`,
    `on_session_start`, `subagent_stop`, etc.) return `PresetError`
    rather than fabricating a phantom file-edit checkpoint.
  - `transcript_source: None`. Hermes' shell-hook payload doesn't
    surface a transcript path, and the on-disk session storage schema
    under `~/.hermes/` is not part of the public hook contract; a
    dedicated reader can land in a follow-up.

Installer (`mdm/agents/hermes.rs`):
  - Writes `[[hooks.<event>]]` entries into `~/.hermes/config.yaml`
    using the documented YAML schema with the `".*"` matcher.
  - Sets `hooks_auto_accept: true` at the root so our installed
    entries don't trigger Hermes' first-use consent prompt — but only
    when the user hasn't explicitly chosen.
  - Idempotent — repeat install is a no-op (compared structurally so
    YAML round-trip whitespace doesn't cause spurious diffs).
  - Token-based ownership matching (`is_git_ai_hermes_command`):
    a hypothetical sibling preset like `hermes-pro` is never
    misidentified.
  - Uninstall removes only hermes entries, removes emptied event
    keys, and removes the `hooks` block when empty; user-defined
    hooks survive.

Tool classification (`bash_tool.rs`):
  - New `Agent::Hermes` variant. `write_file` / `patch` map to
    `FileEdit`, `terminal` maps to `Bash`, all else `Skip`.

New dependency:
  - `serde_yml = "0.0.12"` for parsing `~/.hermes/config.yaml`.
    Hermes is the first supported agent that uses YAML config.

Wiring:
  - `presets/mod.rs::resolve_preset` accepts `hermes`.
  - `mdm/agents/mod.rs::get_all_installers` includes `HermesInstaller`.
    Detection uses `binary_exists` for all three documented entry
    points (`hermes`, `hermes-agent`, `hermes-acp`) plus the
    `~/.hermes` dotfile fallback.
  - `git_ai_handlers` help text lists `hermes`.
  - `tests/integration/repos/test_file.rs::AI_AUTHOR_NAMES` and
    `test_repo.rs::is_known_checkpoint_preset` learn `hermes`.

Tests: 11 preset unit tests, 20 installer unit tests, 9 integration
tests including 3 full E2E flows asserting `assert_lines_and_blame`.
`cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`,
`RUSTDOCFLAGS=\"-D warnings\" cargo doc --no-deps`, and the relevant
test suites all pass locally.

Closes git-ai-project#1136
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

Comment thread tests/integration/hermes.rs Fixed
CodeQL flags the previous `panic!("...", &events[0])` patterns as
cleartext logging of sensitive information because the Debug
serialization of `ParsedHookEvent` includes `external_session_id`.

Drop the `{:?}` argument; the panic message itself already names
the expected variant, so this matches the existing convention used
by gemini, amp, opencode, etc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hermes Support

3 participants