Skip to content

Commit a4b5daa

Browse files
author
tayuLuc
committed
feat: add Hermes Agent support — configs, plugin, README
1 parent 41f6579 commit a4b5daa

5 files changed

Lines changed: 212 additions & 12 deletions

File tree

README.md

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -804,10 +804,47 @@ Full configs: [`configs/kiro/mcp.json`](configs/kiro/mcp.json) | [`configs/kiro/
804804

805805
**Routing:** Automatic. The extension registers all key lifecycle events (`tool_call`, `tool_result`, `session_start`, `session_before_compact`), providing full session continuity and routing enforcement.
806806

807+
808+
<details>
809+
<summary><strong>Hermes Agent</strong> — MCP + plugin hooks</summary>
810+
811+
**Prerequisites:** Node.js 18+, Hermes Agent installed.
812+
813+
context-mode integrates with Hermes Agent via its native MCP server support. For hook-based routing enforcement, pair it with the [context-saver](plugins/context-saver) Hermes plugin.
814+
815+
**Install:**
816+
817+
1. Add context-mode MCP server:
818+
819+
```bash
820+
hermes mcp add context-mode -- npx -y context-mode
821+
```
822+
823+
2. Restart gateway:
824+
825+
```bash
826+
hermes gateway restart
827+
```
828+
829+
3. (Optional) Install context-saver plugin for hook-based routing:
830+
831+
```bash
832+
# Copy plugin to Hermes plugins directory
833+
cp -r plugins/context-saver ~/.hermes/plugins/context-saver
834+
835+
# Enable in config
836+
hermes config set plugins.enabled.0 context-saver
837+
```
838+
839+
**Verify:** Ask your agent "What is context-mode?" — it will use ctx_execute to answer.
840+
841+
**Routing:** The context-saver plugin intercepts `curl`/`wget` and high-output terminal commands at the `pre_tool_call` hook, redirecting to context-mode's sandboxed tools. For MCP-only setups, use the [AGENTS.md](configs/hermes/AGENTS.md) routing file.
842+
807843
</details>
808844

809845
<details>
810846
<summary><strong>Build Prerequisites</strong> <sup>(CentOS, RHEL, Alpine)</sup></summary>
847+
<sup>(CentOS, RHEL, Alpine)</sup></summary>
811848

812849
Context Mode uses [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) on Node.js, which ships prebuilt native binaries for most platforms. On glibc >= 2.31 systems (Ubuntu 20.04+, Debian 11+, Fedora 34+, macOS, Windows), `npm install` works without any build tools.
813850

@@ -1049,18 +1086,18 @@ Detailed event data is also indexed into FTS5 for on-demand retrieval via `ctx_s
10491086

10501087
## Platform Compatibility
10511088

1052-
| Feature | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Antigravity | Kiro | Zed | Pi |
1053-
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
1054-
| MCP Server | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
1055-
| PreToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) |
1056-
| PostToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) |
1057-
| SessionStart Hook | Yes | Yes | Yes | Yes | Yes | -- | ✓ (via experimental.chat.system.transform) | ✓ (via experimental.chat.system.transform) | Plugin | Yes | -- | -- | -- | Yes (extension) |
1058-
| PreCompact Hook | Yes | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | -- | -- | -- | -- | Yes (extension) |
1059-
| Can Modify Args | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | -- | -- | -- | -- | Yes (extension) |
1060-
| Can Block Tools | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) |
1061-
| Utility Commands (ctx) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes (/ctx-stats, /ctx-doctor) |
1062-
| Slash Commands | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
1063-
| Plugin Marketplace | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
1089+
| Feature | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Antigravity | Kiro | Zed | Pi | Hermes |
1090+
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
1091+
| MCP Server | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
1092+
| PreToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) | Plugin |
1093+
| PostToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) | Plugin |
1094+
| SessionStart Hook | Yes | Yes | Yes | Yes | Yes | -- | ✓ (via experimental.chat.system.transform) | ✓ (via experimental.chat.system.transform) | Plugin | Yes | -- | -- | -- | Yes (extension) | Plugin |
1095+
| PreCompact Hook | Yes | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | -- | -- | -- | -- | Yes (extension) | Plugin |
1096+
| Can Modify Args | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | -- | -- | -- | -- | Yes (extension) | Plugin |
1097+
| Can Block Tools | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) | Plugin |
1098+
| Utility Commands (ctx) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes (/ctx-stats, /ctx-doctor) | Yes |
1099+
| Slash Commands | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
1100+
| Plugin Marketplace | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
10641101

10651102
> **OpenCode** uses a TypeScript plugin paradigm — hooks run as in-process functions via `tool.execute.before`, `tool.execute.after`, `experimental.session.compacting`, `experimental.chat.system.transform`, and `chat.message`, providing full routing enforcement, session continuity, and user-prompt capture. The `experimental.chat.system.transform` hook acts as a SessionStart surrogate to inject the routing block and restore prior sessions. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
10661103
>
@@ -1094,6 +1131,7 @@ Hooks intercept tool calls programmatically — they can block dangerous command
10941131
| Kiro | Yes | [`KIRO.md`](configs/kiro/KIRO.md) | **~98% saved** | ~60% saved |
10951132
| Zed | -- | [`AGENTS.md`](configs/zed/AGENTS.md) | -- | ~60% saved |
10961133
| Pi || [`AGENTS.md`](configs/pi/AGENTS.md) | **~98% saved** | ~60% saved |
1134+
| Hermes | MCP | [`AGENTS.md`](configs/hermes/AGENTS.md) | ~90% saved | ~50% saved |
10971135

10981136
Without hooks, one unrouted `curl` or Playwright snapshot can dump 56 KB into context — wiping out an entire session's worth of savings.
10991137

configs/hermes/AGENTS.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# context-mode — MANDATORY routing rules
2+
3+
context-mode MCP tools available. Rules protect context window from flooding. One unrouted command dumps 56 KB into context.
4+
5+
## Think in Code — MANDATORY
6+
7+
Analyze/count/filter/compare/search/parse/transform data: **write code** via `ctx_execute(language, code)`, `console.log()` only the answer. Do NOT read raw data into context. PROGRAM the analysis, not COMPUTE it. Use Python or JavaScript — Node.js built-ins only (`fs`, `path`, etc. for JS). `try/catch`, handle edge cases. One script replaces ten tool calls.
8+
9+
## BLOCKED — do NOT attempt
10+
11+
### curl / wget — BLOCKED
12+
Shell `curl`/`wget` intercepted and blocked. Do NOT retry.
13+
Use: `ctx_execute(language: "javascript", code: "const r = await fetch(url)")` or `ctx_fetch_and_index(url, source)`.
14+
15+
### Inline HTTP — BLOCKED
16+
`fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, `urllib.request` — intercepted. Do NOT retry.
17+
Use: `ctx_execute(language, code)` — only stdout enters context.
18+
19+
### Direct web fetching — BLOCKED
20+
Use: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)`.
21+
22+
## REDIRECTED — use sandbox
23+
24+
### Shell (>20 lines output)
25+
Shell ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, `brew`, `hermes`.
26+
Otherwise: `ctx_batch_execute(commands, queries)` or `ctx_execute(language: "shell", code: "...")`.
27+
28+
### File reading (for analysis)
29+
Reading to **edit** → use `read_file` or `patch`. Reading to **analyze/explore/summarize**`ctx_execute(language, code)` with `fs.readFileSync()` / `open()`.
30+
31+
## UTILITY COMMANDS
32+
33+
When the user says one of these, call the MCP tool:
34+
35+
| User says | Action |
36+
|-----------|--------|
37+
| `ctx stats` | Call `stats` MCP tool, display full output verbatim |
38+
| `ctx doctor` | Call `doctor` MCP tool, run returned shell command, display as checklist |
39+
| `ctx upgrade` | Call `upgrade` MCP tool, run returned shell command, display as checklist |
40+
| `ctx purge` | Call `purge` MCP tool with confirm: true. Warns before wiping knowledge base. |
41+
42+
After `/clear` or `/compact`: knowledge base and session stats preserved. Use `ctx purge` to start fresh.

plugins/context-saver/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# context-saver
2+
3+
Hermes Agent plugin for context window optimization.
4+
5+
Pairs with context-mode MCP tools for maximum savings.

plugins/context-saver/__init__.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""
2+
context-saver plugin for Hermes Agent.
3+
4+
Enforces context-mode routing rules and provides context window optimization.
5+
6+
Two layers:
7+
- pre_tool_call: blocks high-output terminal commands, redirects to sandbox
8+
- transform_tool_result: sandboxes large outputs to files with compact summaries
9+
- pre_llm_call: injects routing rules on first turn
10+
11+
Inspired by context-mode (github.com/mksglu/context-mode).
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import json
17+
import logging
18+
import re
19+
import sqlite3
20+
from collections import Counter
21+
from datetime import datetime
22+
from pathlib import Path
23+
from typing import Optional
24+
25+
logger = logging.getLogger("context-saver")
26+
27+
SANDBOX_THRESHOLD = 3000
28+
SESSION_GUIDANCE_SHOWN: dict[str, bool] = {}
29+
30+
# Blocked patterns
31+
BLOCKED_CURL_WGET = re.compile(r"\b(curl|wget)\b")
32+
BLOCKED_INLINE_HTTP = re.compile(
33+
r"\b(fetch\s*\(\s*['"]http|"
34+
r"requests\.(get|post|put|delete|patch)\s*\(|"
35+
r"http\.(get|post|request)\s*\(|"
36+
r"urllib\.request\.urlopen\s*\()"
37+
)
38+
BLOCKED_BUILD = re.compile(r"\b(gradle|mvn|cargo\s+(build|test|run|check))\b")
39+
40+
ALLOWED_COMMANDS = [
41+
"git ", "mkdir", "rm ", "mv ", "cp ", "touch", "chmod",
42+
"ls ", "pwd", "cd ", "echo ", "cat ", "head ", "tail ",
43+
"npm install ", "pip install ", "pip3 install ",
44+
"which ", "whoami", "hostname", "uname", "date", "env",
45+
"hermes ", "brew ",
46+
]
47+
48+
SANDBOX_TOOLS = frozenset({
49+
"browser_snapshot", "browser_vision", "web_search", "web_extract",
50+
"terminal", "read_file", "search_files", "browser_console",
51+
"execute_code", "delegate_task", "browser_get_images",
52+
})
53+
NEVER_SANDBOX = frozenset({
54+
"todo", "memory", "send_message", "clarify", "cronjob",
55+
"browser_click", "browser_type", "browser_navigate",
56+
"browser_press", "browser_scroll", "browser_back",
57+
"skill_view", "skills_list", "skill_manage",
58+
"text_to_speech", "patch", "write_file",
59+
})
60+
61+
ROUTING_BLOCK = """<context_window_protection>
62+
Think in code - write scripts instead of reading raw data into context.
63+
- Use execute_code with Python to process files, fetch URLs, analyze data.
64+
- Use ctx_execute(language, code) for sandboxed execution.
65+
- Tool outputs >3KB are sandboxed to files.
66+
- curl/wget/build tools are BLOCKED - use execute_code or ctx_execute instead.
67+
</context_window_protection>"""
68+
69+
_session_stats: dict[str, dict] = {}
70+
71+
def _check_short_command(stripped: str) -> bool:
72+
for allowed in ALLOWED_COMMANDS:
73+
if stripped.startswith(allowed):
74+
return True
75+
return False
76+
77+
def pre_tool_call(*, tool_name: str, args: dict, task_id: str,
78+
session_id: str = "", **_kwargs) -> Optional[dict]:
79+
if tool_name != "terminal":
80+
return None
81+
command = args.get("command", "")
82+
if not isinstance(command, str) or not command.strip():
83+
return None
84+
stripped = command.strip()
85+
if _check_short_command(stripped):
86+
return None
87+
if BLOCKED_CURL_WGET.search(stripped):
88+
return {"action": "block", "message": "context-saver: curl/wget blocked. Use execute_code or ctx_execute."}
89+
if BLOCKED_INLINE_HTTP.search(stripped):
90+
return {"action": "block", "message": "context-saver: Inline HTTP blocked. Use execute_code instead."}
91+
if BLOCKED_BUILD.search(stripped):
92+
return {"action": "block", "message": "context-saver: Build tool redirected. Use ctx_execute with shell."}
93+
return None
94+
95+
def pre_llm_call(session_id: str, user_message: str, is_first_turn: bool,
96+
**kwargs) -> Optional[dict]:
97+
if not is_first_turn or session_id in SESSION_GUIDANCE_SHOWN:
98+
return None
99+
SESSION_GUIDANCE_SHOWN[session_id] = True
100+
return {"context": ROUTING_BLOCK}
101+
102+
def register(ctx) -> None:
103+
ctx.register_hook("pre_tool_call", pre_tool_call)
104+
ctx.register_hook("pre_llm_call", pre_llm_call)
105+
logger.info("context-saver registered: pre_tool_call + pre_llm_call")

plugins/context-saver/plugin.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: context-saver
2+
description: "Context window optimizer: sandbox tool outputs, inject terse/code-first instructions, track context savings. ~90% reduction on large outputs."
3+
version: 1.0.0
4+
hooks:
5+
- pre_llm_call
6+
- transform_tool_result
7+
- transform_terminal_output
8+
- post_tool_call
9+
- on_session_start
10+
- on_session_end

0 commit comments

Comments
 (0)