Skip to content

feat: Hermes-native plugin surface for Genie - #2517

Merged
namastex888 merged 4 commits into
devfrom
wish/hermes-khaw-native-surface
Jul 4, 2026
Merged

namastex888 merged 4 commits into
devfrom
wish/hermes-khaw-native-surface

Conversation

@namastex888

Copy link
Copy Markdown
Contributor

Wish: hermes-khaw-native-surface

Ships a native Hermes plugin from this repo (plugins/hermes-genie/) so Hermes can act as the chat/reasoning cockpit while Genie remains the zero-daemon execution system. Strictly read-only MVP: every tool returns mutation: "none" and wraps the genie v5 CLI via argv arrays (no shell strings).

What's included

  • plugin-core — plugin.yaml, register(ctx), argv-only subprocess bridge with shell-metachar rejection + validate_ref traversal guard (incl. symlink-escape defense on the WISH.md read), 7 read-only tools grounded on the v5 CLI: genie_status, genie_board, genie_wish_status (board+task composite), genie_task_list, genie_task_status, genie_work_plan (launch --dry-run), genie_review_plan
  • plugin-surface — /genie slash dispatcher + 4 wrapper commands with outcome-first rendering and evidence footers; advisory hooks (session-start .genie reminder, terminal-scrape advice — never blocking); hasattr-guarded CLI tree + 4 skills
  • plugin-docs — plugin README, references/native-surface.md + mutation-gates.md, install/smoke scripts (symlink default, --copy), Hermes-native cross-links in root README, Claude Code plugin README, and the Hermes profile seed
  • wish + profile seed port — .genie/wishes/hermes-khaw-native-surface/WISH.md; profiles/hermes/genie/ ported verbatim from main (it exists only on main lineage) so the seed README could be modified here — the eventual dev→main merge reconciles on identical blobs except that README (additive edit)

Verification

  • 46 pytest cases green: uv run --with pytest --with pyyaml --no-project python -m pytest plugins/hermes-genie/tests -q
  • Per-group independent reviews: plugin-core FIX-FIRST→SHIP (traversal exploit found, fixed, re-verified adversarially incl. symlink escape), plugin-surface SHIP, plugin-docs SHIP
  • bun run check: typecheck/biome/knip/skills-lint/wishes-lint pass; bun test 655 pass / 2 pre-existing environmental failures (host ~/.genie/config.json omni-approvals leaking into dispatch-fail-closed-regression + omni-dispatch via hardcoded homedir() in src/lib/genie-config.ts — fails on any branch on that host; zero TS changed in this PR)
  • Live dogfood: plugin installed + enabled in Hermes 0.18.0 — /genie help and /genie status execute natively in hermes chat; e2e evidence recorded in the KHAW repo (docs/evidence/genie-hermes-khaw-native-surface-smoke-2026-07-04.md)

Follow-ups (non-blocking, documented)

  • Install script/README: handle Hermes profile-based hosts (sticky active_profile uses ~/.hermes/profiles/<name>/plugins/)
  • --copy install carries __pycache__; KHAW-side extension-point test doesn't pin the forbidden list; bridge timeout has no upper clamp

The KHAW-side bridge (Group 4) lands separately in the KHAW repo (feat/khaw-genie-bridge).

🤖 Generated with Claude Code

https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs

namastex888 and others added 4 commits July 4, 2026 18:12
… seed from main

The profile seed (profiles/hermes/genie/, commit b112309) exists only on
main lineage; ported verbatim so Group 3 can modify the README on this
dev-cut branch. The dev->main merge will reconcile on identical blobs
except the README, which carries the wish's additive edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
…only tools

Hermes-native surface for Genie (wish hermes-khaw-native-surface, Group 1).
7 read-only tools grounded on the v5 CLI (doctor/board/task list/task
status/launch --dry-run), argv-only subprocess bridge with shell-metachar
rejection, validate_ref traversal guard + in-bounds WISH.md read, uniform
{success, mutation:none, cwd, command|source} payload. 28 pytest cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
…scripts, profile seed update

Group 3 of wish hermes-khaw-native-surface. Install script (symlink
default, --copy mode), smoke script, native-surface + mutation-gates
references, Hermes-native cross-links in root README, Claude Code plugin
README, and the Hermes profile seed (additive). Two review LOWs applied:
ln -sfn manual one-liner, payload-contract wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
… CLI tree, skills

Group 2 of wish hermes-khaw-native-surface. /genie dispatcher (+4 wrapper
commands) with outcome-first rendering and evidence footers, advisory
hooks (session-start .genie reminder, terminal-scrape advice, never
blocking), hasattr-guarded CLI tree and 4 path-based skills. 18 new tests
(46 total). Two review LOWs applied: on_session_start degrades on
unresolvable cwd; list-shaped command events joined before advisory match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BEJCsZTjxLyrM8BQKjGEVs
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 76b8dd40-3986-42bb-8926-b3a8e5e87903

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wish/hermes-khaw-native-surface

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@namastex888
namastex888 merged commit 1d0fcc5 into dev Jul 4, 2026
11 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a native Hermes plugin for Genie (plugins/hermes-genie/) that exposes read-only Genie state (such as status, board, wish/task queries, and dry-run plans) as structured tools, slash commands, advisory hooks, and skills. It also includes comprehensive documentation, local installation/smoke scripts, test suites, and a Genie Hermes profile seed. The review feedback focuses on several key improvements: dynamically importing commands and hooks inside registration functions to prevent circular dependency cycles at module load time, robustly locating the genie binary on the system PATH using shutil.which with a fallback path, and cleaning up __pycache__ directories during copy-style installations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +216 to +225
try: # package import (Hermes loads plugins as packages)
from . import commands, hooks
except ImportError: # flat import (module loaded from a file location)
import sys

_HERE = str(Path(__file__).resolve().parent)
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
import commands # type: ignore[no-redef]
import hooks # type: ignore[no-redef]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To adhere to the general rules and break circular dependency cycles at module load time, the imports of commands and hooks should be moved dynamically inside the functions that use them (_register_commands and register) rather than being imported at the module level.

# Circular imports of commands and hooks have been removed from module level
# and moved dynamically inside _register_commands and register to break the load-time cycle.
References
  1. Use dynamic imports (require() or import()) to break circular dependency cycles that would otherwise occur at module load time.

Comment on lines +228 to +230
def _register_commands(ctx) -> None:
"""Slash commands: /genie dispatcher plus per-subcommand aliases."""
command_defs: list[tuple[str, Any, str, str]] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Dynamically import commands inside _register_commands to break the circular dependency cycle at module load time.

def _register_commands(ctx) -> None:
    """Slash commands: /genie dispatcher plus per-subcommand aliases."""
    try:
        from . import commands
    except ImportError:
        import commands
    command_defs: list[tuple[str, Any, str, str]] = [

Comment on lines +293 to +312
if hasattr(ctx, "register_command"):
_register_commands(ctx)

if hasattr(ctx, "register_hook"):
hook_defs: list[tuple[str, Any]] = [
("on_session_start", hooks.on_session_start),
("pre_tool_call", hooks.pre_tool_call),
("post_tool_call", hooks.post_tool_call),
]
for event, handler in hook_defs:
ctx.register_hook(event, handler)

if hasattr(ctx, "register_cli_command"):
ctx.register_cli_command(
name="genie",
help="Genie read-only commands (status, board, wish, work-plan, review-plan)",
setup_fn=commands.setup_cli,
handler_fn=commands.cli_handler,
description="Operate Genie from the Hermes CLI without mutations",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Dynamically import hooks and commands inside register to break the circular dependency cycle at module load time.

    if hasattr(ctx, "register_command"):
        _register_commands(ctx)

    if hasattr(ctx, "register_hook"):
        try:
            from . import hooks
        except ImportError:
            import hooks
        hook_defs: list[tuple[str, Any]] = [
            ("on_session_start", hooks.on_session_start),
            ("pre_tool_call", hooks.pre_tool_call),
            ("post_tool_call", hooks.post_tool_call),
        ]
        for event, handler in hook_defs:
            ctx.register_hook(event, handler)

    if hasattr(ctx, "register_cli_command"):
        try:
            from . import commands
        except ImportError:
            import commands
        ctx.register_cli_command(
            name="genie",
            help="Genie read-only commands (status, board, wish, work-plan, review-plan)",
            setup_fn=commands.setup_cli,
            handler_fn=commands.cli_handler,
            description="Operate Genie from the Hermes CLI without mutations",
        )

Comment on lines +9 to +14
import json
import os
import re
import subprocess
from pathlib import Path
from typing import Any

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Import shutil to support locating the genie binary on the system PATH.

Suggested change
import json
import os
import re
import subprocess
from pathlib import Path
from typing import Any
import json
import os
import shutil
import re
import subprocess
from pathlib import Path
from typing import Any

Comment on lines +85 to +88
workdir = resolve_cwd(cwd)
try:
argv = build_genie_argv(args)
proc = subprocess.run(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since genie is not reliably on the system PATH (especially on macOS where it defaults to ~/.genie/bin/genie), we should attempt to locate it using shutil.which and fall back to the default installation path if it is not found on PATH. This ensures the plugin works robustly across different environments.

Suggested change
workdir = resolve_cwd(cwd)
try:
argv = build_genie_argv(args)
proc = subprocess.run(
workdir = resolve_cwd(cwd)
try:
argv = build_genie_argv(args)
if not shutil.which(argv[0]):
fallback = Path.home() / ".genie" / "bin" / "genie"
if fallback.is_file() and os.access(fallback, os.X_OK):
argv[0] = str(fallback)
proc = subprocess.run(

Comment on lines +49 to +51
if [ "$mode" = "copy" ]; then
cp -R "$plugin_src" "$target"
echo "installed (copy): $target (from $plugin_src)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When performing a detached copy-style installation, __pycache__ directories from the development environment can be copied over. Clean them up from the target directory after copying.

Suggested change
if [ "$mode" = "copy" ]; then
cp -R "$plugin_src" "$target"
echo "installed (copy): $target (from $plugin_src)"
if [ "$mode" = "copy" ]; then
cp -R "$plugin_src" "$target"
find "$target" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
echo "installed (copy): $target (from $plugin_src)"

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 314f3a724a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


def pre_tool_call(event: Any = None, **kwargs: Any) -> dict[str, Any]:
"""Advise (never block) when a tool call looks like scraping or polling Genie."""
raw = _event_value(event, "command") or _event_value(event, "args") or ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Read Hermes hook kwargs for terminal advice

In the Hermes plugin hook API, pre_tool_call is invoked with tool_name, args, and task_id keyword arguments (the docs/example call invoke_hook(..., tool_name="terminal", args=args, ...)), not an event object containing command. With the real call shape, a terminal scrape like args={"command":"tmux capture-pane -p"} leaves raw empty here and the advertised advisory hook never fires; keep the event-object fallback, but also inspect kwargs["args"]/kwargs["tool_name"].

Useful? React with 👍 / 👎.

echo "installed (copy): $target (from $plugin_src)"
else
ln -s "$plugin_src" "$target"
echo "installed (symlink): $target -> $plugin_src"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enable the plugin after installing it

This script stops immediately after creating the symlink/copy, but Hermes general plugins are opt-in per the plugin guide (hermes plugins enable <name> is required when a plugin is “not enabled in config”). A fresh user following the README will have files under $HERMES_HOME/plugins/genie but the tools/commands/hooks remain unloaded until they manually enable it, so the installer should run or at least print the required hermes plugins enable genie step.

Useful? React with 👍 / 👎.

@automagik-genie
automagik-genie deleted the wish/hermes-khaw-native-surface branch September 25, 2026 04:51
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.

1 participant